Python to Native Compiler Tutorial: Py2Native Step by Step
If you’re shipping Python code to customers, you know the problem: the .py files are readable, copyable, and modifiable. Py2Native compiles your own Python code into native machine code—no Cython syntax, no hand-written C, no manual cythonize step. This tutorial walks you through protecting a simple Python application with Py2Native, from first build to optional Pro license verification.
By the end, you’ll have a native executable (or shared library) built from plain Python, and you’ll know how to add JWT-based license checks with the Pro plugin. For background on why native compilation matters, see What Is Python to Native Compilation? A Beginner’s Guide.
Prerequisites
Before you start, make sure you have:
- Python 3.11–3.15 (Py2Native supports free-threaded 3.14t and 3.15t as well)
- A C compiler and linker for your platform:
- Windows: MSVC (Visual Studio Build Tools)
- Linux: GCC
- macOS: Clang (Xcode Command Line Tools)
- uv (the package manager used by Py2Native) — not pip. If you don’t have
uv, install it from the official uv docs. - Internet access during the first build, because Py2Native downloads Python distributions and libraries.
Py2Native itself is run via uv run py2native and does not require a global installation. The open-source core is MIT licensed and available on PyPI.
Step 1: Verify Py2Native
Open a terminal and run:
uv run py2native --help
If everything is set up, uv will fetch the py2native package on first use and print the CLI help. You don’t need to create a virtual environment or run pip install; uv run handles the environment automatically.
You’ll see commands like build, keygen, sign, and show. The keygen, sign, and show commands are only available with the Pro plugin (py2nativepro), which we’ll use later.
Step 2: Prepare a Small Python Project
Create a project with a main script and a package module. For this tutorial:
license_demo/
├── main.py
└── mypackage/
└── secret.py
main.py:
from mypackage.secret import get_secret
def main():
print("Secret:", get_secret())
if __name__ == "__main__":
main()
mypackage/secret.py:
def get_secret() -> str:
# This string should not be visible as plain text in the shipped artifact.
return "Py2Native-protected-42"
That’s it. Py2Native compiles plain Python—no .pyx files, no C extension boilerplate, no changes to your code.
Step 3: Compile to a Native Executable
From the project root, run:
uv run py2native build main.py mypackage/*.py
Py2Native will:
- Expand the glob patterns and collect all source files.
- Run the build orchestrator, which uses Cython under the hood to transpile each
.pyfile to C. - Compile the generated C to object files with your platform’s C compiler.
- Link everything into a native executable.
The output appears in the build directory (by default build/). On Windows you’ll get a .exe, on Linux an ELF binary, and on macOS a Mach-O executable.
If you want a deployment directory or a distributable wheel, use the optional flags:
# Create a uv-managed deployment directory with the binary and required runtime files
uv run py2native build main.py mypackage/*.py --embed dist/deploy
# Create a Python wheel containing compiled extension modules
uv run py2native build main.py mypackage/*.py --wheel dist/wheel
For a shared library instead of an executable, add --library. Py2Native will produce a .pyd/.so/.dylib plus __init__.py and __main__.py so the package can be imported and run with python -m mypkg.
Third-party libraries you use remain as Python source and work as-is. Only your custom code is compiled, which keeps setup minimal.
Step 4: Run and Verify the Compiled Binary
Run the generated executable from the build directory:
./build/main
On Windows:
.\build\main.exe
You should see:
Secret: Py2Native-protected-42
To confirm the binary is native, use the file command on Linux or macOS:
file build/main
Example output on Linux:
build/main: ELF 64-bit LSB executable, x86-64, dynamically linked, ...
On macOS you might see Mach-O 64-bit executable. On Windows, checking the file’s PE header with a tool like dumpbin or simply observing that it’s an .exe confirms native compilation.
More importantly, the secret string is no longer present as readable Python source inside the binary. You can search the binary for "Py2Native-protected-42" and you won’t find it as plain text.
Step 5: Add License Verification (Pro)
If you ship commercial software, you may want to require a valid license. The Py2Native Pro plugin (py2nativepro) adds JWT-based license verification with EC P-256 signatures. The verification logic and the public key get baked into the executable.
5.1 Generate a key pair
Run:
uv run py2native keygen private.pem public.pem
This creates an EC P-256 key pair. Keep private.pem secure; only public.pem is embedded in your executable.
5.2 Sign a license token
Create a JWT payload with the required claims. The Pro plugin verifies the issuer and audience you specify at runtime, so these must match later. For example:
uv run py2native sign --private private.pem '{"iss":"RSJ Software GmbH","aud":"TimestampGIT","exp":1893456000}' license.dat
The exp value is a Unix timestamp (this example expires on 2030-01-01). The output license.dat is the signed JWT.
You can inspect the token with:
uv run py2native show --public public.pem license.dat
This displays the JWT claims and verifies the signature against the public key.
5.3 Add the .pxd declaration and license check to your code
Py2Native Pro requires a .pxd file that imports the runtime verification function from the internal bootstrap module. Create a file named license_verify.pxd in your project root:
from _p2n_bootstrap cimport _runtime_verify_es256_jwt
cdef _runtime_verify_es256_jwt(token, expected_iss=*, expected_aud=*)
This declares the C-level function that the Pro plugin injects into your compiled code.
Then add the license check to main.py:
import sys
def read_license_file(path: str = "license.dat") -> str:
with open(path, "r") as f:
return f.read().strip()
def main():
license_string = read_license_file()
license = _runtime_verify_es256_jwt(
license_string,
expected_iss="RSJ Software GmbH",
expected_aud="TimestampGIT"
)
if not license:
print("Invalid license.")
sys.exit(1)
print("License verified. Proceeding...")
if __name__ == "__main__":
main()
Notice the call to _runtime_verify_es256_jwt(...) directly—no import from the .pxd file is needed in the Python source; the function is available at compile time through the bootstrap.
5.4 Build with --license and --public
Pass the signed license file and the public key to the build command, and include the .pxd file as a source:
uv run py2native build main.py mypackage/*.py license_verify.pxd \
--license license.dat \
--public public.pem
The Pro plugin verifies that you have a valid Pro license, bakes the public key and verification code into the executable, and then compiles your code. At runtime, the executable checks the license file against the embedded key and expected claims.
Only the public key is stored in the executable; the private key never leaves your build machine.
Troubleshooting Common Issues
Missing C compiler
Windows: Install Visual Studio Build Tools with the “Desktop development with C++” workload, then open a Developer Command Prompt.
Linux: Install gcc and the Python development headers. On Debian/Ubuntu:
sudo apt update && sudo apt install build-essential python3-dev
macOS: Install Xcode Command Line Tools:
xcode-select --install
Module name conflicts
Py2Native doesn’t support modules with identical names across different source files. Keep top-level module names unique. If you have utils.py in two packages, rename one or use package-relative imports.
LGPL third-party libraries
Py2Native leaves third-party libraries as Python source, including LGPL libraries. That means end users can replace those libraries if needed—this is a compliance feature, not a bug. Your custom code remains compiled.
License verification fails
- Check that the JWT’s
issandaudmatch exactly what you pass to_runtime_verify_es256_jwt. - Confirm the license file path is correct and the file contains the signed JWT.
- Run
uv run py2native show --public public.pem license.datto verify the token and signature. - Make sure the build command includes
--licenseand--publicwith the correct files.
FAQ
What is the difference between Py2Native and PyInstaller?
PyInstaller bundles Python bytecode and an interpreter into an executable, but the bytecode can be decompiled. Py2Native compiles your Python code to native machine code using Cython under the hood, making it much harder to reverse engineer. Py2Native also offers optional license verification with the Pro plugin.
Can I compile Python code that uses third-party libraries?
Yes. Py2Native leaves third-party libraries as Python source and they work as-is with minimal or no configuration. Only your custom Python code is compiled to native code.
How does license verification work in Py2Native Pro?
The Pro plugin adds JWT-based license verification. You generate an EC P-256 key pair, sign a JWT with the private key, and include the public key and verification code in the compiled binary. At runtime, your code calls _runtime_verify_es256_jwt with the license string and expected issuer/audience to validate the license.
Is Py2Native free to use?
The core Py2Native compiler is open source under the MIT license and free to use. The Pro plugin, which adds license verification and string compression, is a commercial product.
Conclusion
Py2Native gives you a zero-config path from plain Python to native machine code: install nothing globally, run uv run py2native build, and get a protected executable or library. This tutorial covered the full loop—verifying the CLI, preparing a project, compiling, running the binary, and adding Pro license verification with signed JWTs.
The key takeaway is that you don’t need to learn Cython, write C extensions, or manage a complex build. Your Python code stays Python; Py2Native handles the compilation pipeline under the hood. If you’re shipping proprietary Python software, this is the most direct way to keep your source code out of the hands of customers.
Ready to try it? Start with the community edition, and if you need license verification, the Pro plugin is available at Py2Native.
Related posts
- What Is Python to Native Compilation? A Beginner’s Guide
- Best Python Code Protection Tools in 2025: Top Picks
- Python Code Protection Without Cython Syntax: Py2Native vs. Raw Cython