Skip to content

How To: Control Which Modules Are Protected

This guide explains how PyLocket decides which Python modules to protect and how you can override that behavior.


Default Behavior: Automatic Module Classification

PyLocket automatically classifies every module in your artifact as either user code (protected) or dependency code (skipped). You do not need to configure anything for typical projects — PyLocket protects your code and leaves everything else untouched.

What Gets Protected by Default

  • Your application's entry module (the main script)
  • All user-written helper modules reachable from the entry module
  • Standalone .py files that are part of your project

What Is Automatically Skipped

  • Python standard library modules (os, sys, json, pathlib, etc.)
  • PyInstaller internals (pyimod, pyiboot, pyi_rth_*)
  • Common third-party packages (requests, flask, django, pydantic, click, rich, tqdm, setuptools, pip, certifi, urllib3, etc.)
  • C extension modules (.pyd, .so files compiled from C/Cython)

This means PyLocket protects only your proprietary code by default — the parts that contain your business logic and intellectual property.


Why You Should Not Protect Every Module

Do not force-include third-party libraries

Protecting third-party dependencies is unnecessary and can cause problems:

  • Increased build time and artifact size — encrypting thousands of library functions adds overhead with no security benefit
  • Potential runtime errors — some libraries use reflection, dynamic imports, or introspection that may not work correctly when function bodies are replaced with stubs
  • No IP value — third-party libraries are already publicly available; encrypting them does not protect your intellectual property

Focus protection on the code that is uniquely yours: your application logic, algorithms, business rules, and proprietary modules.


Overriding Module Selection

PyLocket's heuristics cover the common case, but you have the final say. Declare the modules or functions you want protected and PyLocket will protect them even when its classifier would have skipped them.

There are three ways to declare this. They do the same thing; pick whichever fits how you build.

Create pylocket.toml at the root of your project:

[protect]
functions = [
  "myapp.data.ledger_key.ledger_key",
  "myapp.services.billing",
]
exclude = ["myapp.debug_tools"]

The CLI finds it automatically, including when you run from a subdirectory:

pylocket protect --app <APP_ID> --artifact dist/myapp.exe --platform win-x64

This is the recommended route because the declaration is versioned with your code, shows up in a pull-request diff, and needs nobody in a browser at release time.

2. The REST API

If you build through the API rather than the CLI, send the same lists when you create the build:

curl -X POST https://api.pylocket.com/v1/apps/$APP_ID/builds \
  -H "X-API-Key: $PYLOCKET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "version": "1.2.1",
    "artifact_type": "app",
    "platform": "macos-arm64",
    "force_include": ["myapp.data.ledger_key.ledger_key"],
    "force_exclude": ["myapp.debug_tools"]
  }'

3. A one-off flag

To override the manifest for a single build:

pylocket protect --app <APP_ID> --artifact dist/myapp.exe \
  --protect myapp.data.ledger_key.ledger_key \
  --protect myapp.services.billing

Use --no-protect-manifest to ignore pylocket.toml entirely for one run.

What you can name

Form Example Effect
Package myapp Protects every module in the package
Module myapp.services.billing Protects that module
Function myapp.data.ledger_key.ledger_key Protects the module and verifies that function was protected

PyLocket protects at module granularity: every eligible function in a selected module is protected. Naming a function additionally asserts it, which is the reason to prefer the function form for anything that matters.

A name that cannot be honoured fails the build

If you name a function and it does not end up protected, the build fails and tells you which name and why:

Declared protection targets were not protected:
'myapp.data.ledger_key.ledgr_key' (function was not protected).

This is deliberate. A typo that silently protected nothing would leave you shipping in the belief that you were covered.

A function is skipped when it is a closure (it reads a variable from an enclosing function) or is below the minimum size. Move the value into a module-level function if you hit either.

What you cannot force-include

Naming a standard-library, PyInstaller-internal or known third-party package has no effect. Those checks run before your declaration, so an app that happens to be named after a dependency can never claim it. Protecting a dependency's body has no upside and a real downside: it breaks apps at import time.

Precedence

--protect beats pylocket.toml, which beats the per-app default. Excludes are evaluated before includes, so an exclude always wins.

Environment variables (local engine runs only)

PYLOCKET_FORCE_INCLUDE and PYLOCKET_FORCE_EXCLUDE are read by the protection engine itself.

These do not work with the hosted service

The engine runs on PyLocket's build infrastructure, not on your machine, so setting these in front of pylocket protect has no effect: the CLI only uploads your artifact. Earlier versions of this page showed them as the way to override selection, which was wrong. Use pylocket.toml, the REST API field, or --protect instead.


Classification Priority

When PyLocket evaluates a module, it follows this priority order:

  1. Force-include override → Module is protected
  2. Force-exclude override → Module is skipped
  3. Entry module → Protected
  4. Not reachable from entry module → Skipped
  5. Standard library module → Skipped
  6. PyInstaller / packaging tool internal → Skipped
  7. Known third-party package → Skipped
  8. Standalone, reachable user module → Protected

Tips

  • Start with defaults — PyLocket's automatic classification works correctly for the vast majority of projects. Only use overrides if you encounter a specific issue.
  • Check the build's protection report — GET /v1/apps/{app_id}/builds/{build_id} returns manifest_data.protection_log (which modules were protected and which were skipped) alongside functions_protected, modules_protected, modules_skipped and skipped_by_reason. Your CI can assert on these without unpacking the artifact.
  • Separate your code from dependencies — Keeping your project structure clean (your code in a distinct package, dependencies installed via pip) helps PyLocket classify modules accurately.

See Also