Py2Native Compile custom Python into native machine code to protect proprietary code

← All posts

2026-08-28

Python to Native Compiler: Protect Your Code Effortlessly

Python to Native Compiler: Protect Your Code Effortlessly
python compiler cython code protection open source

Python to Native Compiler: Protect Your Code Effortlessly

You need to ship a Python application, but you do not want to hand over readable source code. That is the core job of a Python to native compiler: convert your custom Python into native machine code, then ship the compiled result instead of the .py files. The problem with many tools is that they expose the hard parts of Cython: .pyx files, .pxd declarations, manual cythonize steps, compiler flags, and build matrices.

Py2Native takes a different path. It uses Cython under the hood but hides the entire workflow. You write plain Python, run one uv run py2native build command, and get a native executable or shared library. Third-party libraries remain Python source and keep working as-is.

By the end of this walkthrough, you will compile a multi-file Python project into a native binary, optionally embed a Python runtime for portability, optionally build an importable package, and verify that the compiled output behaves like the original scripts.

Prerequisites: What You Need Before Compiling

Before running Py2Native, make sure the host machine has:

  • CPython 3.11 through 3.15, including free-threaded 3.14t and 3.15t.
  • A platform C compiler and linker:
    • MSVC on Windows.
    • GCC on Linux.
    • Clang on macOS.
  • Internet access during the build, because Py2Native downloads Python distributions and required libraries.
  • uv available on PATH. Py2Native uses uv as its package manager and execution layer.
  • A Python project with a main entry point and any additional modules you want compiled.

The examples below use uv run py2native so the commands are self-contained. If you prefer a standalone CLI installation, install Py2Native once with:

uv tool install py2native

Then verify the CLI is available:

uv run py2native --help

Step-by-Step: Compiling Your Python Project with Py2Native

The build command follows a simple pattern:

uv run py2native build <mainModule> <source>...

The first argument is the main module. The remaining arguments are source files or glob patterns.

Step 1: Organize Your Project

Start with a small project. This example has a main entry point and two application modules:

# main.py
from config import APP_NAME
from utils import clean_text


def main():
    value = input("Enter a value: ")
    print(f"{APP_NAME}: {clean_text(value)}")


if __name__ == "__main__":
    main()
# config.py
APP_NAME = "Demo"
# utils.py
def clean_text(value: str) -> str:
    return value.strip().title()

One rule matters here: no two source files can define modules with the same name. If you have duplicate module names, rename one before compiling.

Step 2: Build a Native Executable

From the project directory, run:

cd myapp
uv run py2native build main.py *.py

Py2Native expands the glob, dispatches the compiler pipeline, and produces a native executable. Behind the scenes, the pipeline converts Python to C with Cython, compiles the C to native objects, and links the result for the current platform.

If the sources live under a specific directory, use --base:

uv run py2native build main.py '*.py' --base src

The output path is printed by the build. On Windows that is typically a .exe file; on Linux and macOS it is a native ELF or Mach-O binary. Yes, the pipeline still uses Cython internally, but there are no .pyx files for you to maintain and no manual cythonize step. That is the hard way; Py2Native handles it.

Step 3: Create a Shared Library Instead of an Executable

If you want to distribute the code as an importable package rather than a standalone executable, use --library:

uv run py2native build main.py *.py --library

Library mode creates a shared library: .pyd on Windows, .so on Linux, or .dylib on macOS. The generated package includes:

  • A single compiled shared library containing all compiled modules.
  • __init__.py, which bridges Python’s import system to the native library.
  • __main__.py, which enables python -m mypkg as a runnable entry point.

Both generated Python files are required in library mode. You do not write them by hand.

Step 4: Embed a Python Runtime for Portable Deployments

A native executable still needs a compatible Python runtime on the target machine. To remove that requirement, use --embed:

uv run py2native build main.py *.py --embed deploy/myapp

This creates a uv-managed deployment directory under deploy/myapp. The directory contains the compiled binary plus the runtime dependencies it needs. Ship that directory to a machine without Python installed, and the binary still runs.

Step 5: Build a Wheel for Standard Package Distribution

If you publish Python packages internally or to an index, add --wheel:

uv run py2native build main.py *.py --wheel dist

Py2Native creates a PEP 427 wheel containing the compiled .pyd or .so file. This is useful when you want a normal importable package but need its custom Python modules compiled to native code.

Step 6: Add License Verification with the Pro Plugin

The Community edition is MIT licensed and fully functional for source-code protection. If you need to control distribution, the closed-source Pro plugin adds signed JWT license verification.

First, generate an EC P-256 keypair:

uv run py2native keygen private.pem public.pem

Next, sign a JSON payload with the private key:

cat > payload.json <<'JSON'
{"sub":"customer-123","exp":"2027-08-28T00:00:00Z"}
JSON

uv run py2native sign --private private.pem payload.json license.dat

Then build with both the license file and the public key:

uv run py2native build main.py *.py --license license.dat --public public.pem

The Pro plugin bakes the verification code and public key into the executable. Only the public key is stored in the binary, and the elliptic-key verification is handled in compiled code without third-party libraries.

For an in-code license check, Py2Native Pro generates a .pxd declaration as part of the build. You do not maintain Cython files by hand. A generated declaration follows this pattern:

# py2nativepro_license.pxd — generated by Py2Native Pro
cdef extern from "py2nativepro_license.h":
    int py2nativepro_verify_license(const char* license_file, const char* public_key_file)

Your application code calls the generated wrapper:

# main.py
from py2nativepro_license import py2nativepro_verify_license

LICENSE_FILE = "license.dat"
PUBLIC_KEY_FILE = "public.pem"


def main():
    if py2nativepro_verify_license(LICENSE_FILE, PUBLIC_KEY_FILE) != 0:
        raise SystemExit("License verification failed")

    print("License verified. Starting application.")


if __name__ == "__main__":
    main()

The exact generated module and function names appear in the Pro build log; the pattern above is what the plugin produces.

Step 7: Run the Compiled Output

Run the binary printed by the build command. On Linux or macOS:

./build/main

On Windows:

build\main.exe

The compiled binary should behave like the original Python script. If anything fails, the next two sections cover verification and troubleshooting.

Verifying the Compilation Worked

Do not rely on the build completing without errors. Confirm the output behaves correctly.

  1. Check the output directory. The build log prints where the executable or shared library was written. Look for the expected native artifact.

  2. Run the executable. Execute it from the command line and compare the output with running the original main.py under CPython.

  3. Test library mode in a fresh environment. In a directory containing the generated package, run:

    python -m mypkg
    

    This confirms that __init__.py and __main__.py are present and importable.

  4. Test embedded output. Move into the embed directory and run the binary from there:

    cd deploy/myapp
    ./build/main
    

    This confirms the runtime dependencies are bundled with the binary.

  5. Test Pro license verification. Run the binary with a valid license.dat, then replace it with an invalid or expired license. The valid case should continue; the invalid case should exit with License verification failed.

Troubleshooting Common Issues

C compiler not found

If compilation fails early with a compiler or linker error, install the platform toolchain:

  • Windows: install MSVC Build Tools.
  • Ubuntu or Debian: sudo apt install build-essential.
  • macOS: xcode-select --install.

Module name conflicts

Py2Native intentionally prevents modules with identical names across source files. If the build reports duplicate module names, rename one of the files and try again.

Third-party libraries misbehave

Py2Native leaves third-party libraries as Python source, so they work as-is on the target Python environment. If an import fails after compilation, check that the library supports the embedded Python version in your --embed directory. The issue is usually version compatibility, not the native compilation step.

Binary size is too large

Embedding a Python runtime increases deployment size. If size is more important than a single-directory deployment, use --library or --wheel with a known Python environment on the target machine.

License verification fails

Check three things:

  • The license file was signed with the matching private key.
  • The --public flag points to the public key generated from that private key.
  • The JWT has not expired, and the machine clock is accurate.

FAQ: Python to Native Compiler Questions Answered

What is a Python to native compiler?

A Python to native compiler converts Python source code into native machine code, such as executables or shared libraries. That makes the compiled code harder to reverse-engineer and allows distribution without exposing source code.

Does Py2Native require me to write Cython code?

No. Py2Native uses Cython under the hood but hides it completely. You write plain Python, and Py2Native handles the transpilation and compilation automatically.

Can I compile a Python project that uses third-party libraries?

Yes. Py2Native leaves third-party libraries as Python source, so they work as-is. Only your custom Python code is compiled to native code.

How do I add license verification to my compiled binary?

With the Pro plugin, you generate an EC keypair, sign a JWT payload with the private key, and build with the --license and --public flags. The verification code and public key are baked into the executable.

Conclusion: Protect Your Python Code with Zero-Config Compilation

Py2Native turns your custom Python code into native machine code with a single uv run py2native build command. There is no Cython expertise required, no manual build matrix, and no need to translate your application into extension modules. You write Python, Py2Native compiles it.

The result is a compiled binary or shared library that protects your source code, simplifies distribution, and leaves third-party libraries working without extra configuration. If you need license enforcement on top of compilation, the Pro plugin adds signed JWT verification with the public key baked into the executable.

Ready to try it? Start with a small project and run:

uv run py2native build main.py *.py

For a faster first compile and a quick-start walkthrough, see the Py2Native docs or the related posts below.

Related posts

EU label: AI-generated content