How to Protect Python Source Code: A Step-by-Step Guide
Python ships as source. When you send a customer a wheel, a zip, or a Docker image, every .py file inside is readable. That makes proprietary algorithms, pricing rules, API keys, and business logic easy to reverse engineer.
Py2Native changes that. It compiles your custom Python code into native machine code while leaving third-party libraries alone. You write plain Python; you get a protected binary, shared library, wheel, or self-contained deployment directory. In this guide, you’ll turn a normal Python project into a compiled artifact and verify that your source is no longer exposed.
Prerequisites
Before you start, make sure you have:
- CPython 3.11–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
- uv installed as your package manager
- A supported OS: Windows 8+, manylinux2014/musl Linux, or macOS
- An x86-64 or ARM64 CPU
- Internet access, because Py2Native may download Python distributions and libraries
You also need a basic Python project with a main entry point. The project can be as simple as a main.py and a package directory.
Step-by-Step: Compiling Your Python Code
The workflow is intentionally boring: install the tool, run one command, and let Py2Native handle the Cython transpilation, C compilation, and linking. You are not writing .pyx files, .pxd declarations, or C extensions. You could hand-write Cython and C extensions, but that’s the hard way; Py2Native keeps the plain Python workflow.
Step 1: Install Py2Native with uv
Install the open-source compiler package with uv:
uv tool install py2native
All build commands in this guide use the uv run py2native form. No pip installs here.
Step 2: Run your first native build
Navigate to your project directory and point Py2Native at your main module and source files:
cd myapp
uv run py2native build main.py *.py
Py2Native expands the glob patterns under your project base directory. If all of your application code lives inside main.py, that command is enough. If your code is spread across modules, include the files or globs that contain your custom code.
Step 3: Understand what the build actually did
Under the hood, Py2Native runs through a compilation pipeline:
- Glob sources — it finds and expands your source patterns
- Plugin dispatch — optional Pro license checks run here
- Cython to C — the compiler generates a bootstrap module and transpiles your Python to C
- C to objects — a platform C compiler turns the generated C into object files
- Link — Py2Native links the objects into a native executable or shared library
The result is a native binary that contains your compiled Python code. Your original .py files are not required at runtime.
Step 4: Build a shared library instead of an executable
If you want to import your code as a normal Python module after compilation, use --library:
uv run py2native build main.py mypkg/*.py --library
This creates a shared library: .pyd on Windows, .so on Linux, or .dylib on macOS. The build also emits __init__.py and __main__.py so Python’s import system can still load your package. The __init__.py redirects import mypkg.submodule to the compiled shared library, while __main__.py enables python -m mypkg.
Step 5: Create a distributable wheel
To produce a PEP 427 wheel that contains the compiled code, add --wheel:
uv run py2native build main.py mypkg/*.py --library --wheel dist/
The wheel contains the compiled shared library plus the generated import bridge files. Third-party libraries such as requests or numpy are not compiled; they remain normal Python packages and need to be installed in the target environment.
Step 6: Build a self-contained deployment directory
For an application that should run as its own environment, use --embed:
uv run py2native build main.py mypkg/*.py --embed myapp/
Py2Native creates a uv-managed deployment directory. This gives you a native binary plus an embedded Python runtime for the third-party libraries your application still needs.
Step 7: Add Pro license verification
If you ship to customers and need to enforce licensing, the closed-source py2nativepro plugin adds JWT license verification and string compression. The core compiler stays MIT-licensed; Pro is separate.
First, generate an EC P-256 keypair:
uv run py2native keygen private.pem public.pem
Then sign a JWT payload with the private key:
uv run py2native sign --private private.pem <payload> license.dat
The Pro plugin bakes the signature verification code and the public key into the executable. Only the public key is stored in the binary; your private key never leaves your build machine.
For a Pro build, include a .pxd declaration in your project so the plugin can bind the compiled verification routine into the executable:
# license_verify.pxd
cdef public bint py2native_verify_license(const char* license_path, const char* public_key_path) except -1
Then call the verification logic with the license key from your Python code:
from py2native_license import verify_license
if not verify_license("license.dat", "public.pem"):
raise SystemExit("License verification failed")
Finally, build with the license and public key flags:
uv run py2native build main.py mypkg/*.py \
--license license.dat \
--public public.pem
The Pro build enforces the license at startup. If the JWT is expired, the signature is invalid, or the public key path is wrong, verification fails.
Verifying the Protection
Compilation only matters if you can confirm two things: the output runs correctly, and your source is not sitting in plain text inside the binary.
1. Run the native output
Run the executable from the build directory:
./myapp
On Windows, run myapp.exe. If you built with --library, import the package from a fresh Python environment and call its entry point:
python -c "import mypkg; mypkg.main()"
2. Check that the source is not plain text
Use strings on Linux or macOS, or a hex editor on Windows, to search for a distinctive string from your source code:
strings dist/myapp | grep -i "def calculate_discount"
You should not see the Python function source. If you do find large amounts of readable source, the wrong files may have been included in the build glob.
3. Test license enforcement
If you used Pro, check the JWT claims:
uv run py2native show --public public.pem license.dat
Then run your application with a valid license and again with an invalid or expired license. A valid license should let the application start; an invalid one should stop it.
Troubleshooting Common Issues
Compilation fails with a missing C compiler
Py2Native needs a platform toolchain. Install MSVC Build Tools on Windows, build-essential and gcc on Debian/Ubuntu Linux, or Xcode Command Line Tools on macOS.
Module name conflict
Py2Native cannot compile two source files that define the same module name. Rename one of the modules so every module name is unique across your source tree.
A third-party library stops working
Third-party libraries are intentionally left as Python source. They are not compiled, and they will not be embedded unless you provide them. Install the library in the target environment, or include it in your deployment directory when using --embed.
License verification fails
Check the JWT expiration, signature validity, and the path to the public key. Also confirm that you are using the same public key that matches the private key used to sign license.dat.
FAQ
Does Py2Native work with all Python libraries?
Py2Native compiles your custom Python code into native machine code. Third-party libraries are left unmodified and work as-is, as long as they are installed in the target environment. This means you can use popular libraries like requests, numpy, and others without additional configuration.
Can I still use my code as a normal Python module after compilation?
Yes. When you compile with the --library flag, Py2Native produces a shared library (.pyd, .so, or .dylib) along with an __init__.py that redirects imports to the native code. You can import your package as usual, but the implementation is compiled.
Is Py2Native free to use?
The core Py2Native compiler is open source and MIT licensed, so you can use it freely. There is also a commercial Pro plugin that adds license verification features such as JWT signing and verification for teams that need to control distribution.
How does Py2Native compare to obfuscation tools?
Obfuscation tools only make your code harder to read, but they can often be reversed. Py2Native compiles your Python code to native machine code, which is much more difficult to reverse engineer. It provides a higher level of protection while keeping your development workflow simple.
Conclusion
Protecting Python source code doesn’t have to mean rewriting your application or hand-maintaining C extensions. With Py2Native, the workflow is:
- Install with uv
- Run
uv run py2native build - Verify the native output and optional license enforcement
The core compiler turns your plain Python into native machine code, and the Pro plugin adds JWT license verification when you need controlled distribution. If you’ve been relying on obfuscation, you may find that compilation finally closes the gap.
Try it on your own project: Py2Native.
Related posts
- How to Get a Py2Native Pro License
- Challenges Faced by Python Developers Shipping Proprietary Software
- Python Obfuscation Techniques: Do They Really Protect Your Code?