Python integration
Use Python’s standard-library ctypes module to call the Babel native licensing runtime. This guide follows the Python example , including function declarations, handle cleanup and a runnable application directory.
Babel Licensing Native SDK 12.2.0 (C ABI 2) is available for macOS arm64, Linux x64 and Windows x64. The setup script downloads the pinned archive for your platform and verifies its SHA-256 before installation.
Select your platform
The SDK distributes one runtime library for each platform. Botan is embedded; users do not install or ship a Botan or SQLite library. Windows also receives a link-time import library for MSVC/Rust; that file is not another runtime DLL.
| Platform package | Runtime file | Build environment |
|---|---|---|
| macOS arm64, macOS 15+ | .sdk/lib/libBabelLicensingNative.dylib | Apple Silicon toolchain; Xcode command-line tools for compiled clients |
| Linux x64, glibc | .sdk/lib/libBabelLicensingNative.so | x86_64 compiler/toolchain; Ubuntu 22.04 is the initial build baseline |
| Windows x64 | .sdk/bin/BabelLicensingNative.dll | x64 process; MSVC import library in .sdk/lib/BabelLicensingNative.lib |
The installer selects the current OS/architecture, verifies that platform’s archive checksum and metadata, and rejects a different package. Linux checks its minimum glibc version; a musl/Alpine package is not provided. The native runtime uses the OS certificate trust store and system facilities. The release archives are available from the 12.2.0 SDK release .
Linux: install, build and run
Install Python 3.9 or later and the language toolchain. Rust needs Cargo/rustc 1.85 or newer plus a C linker; Go needs Go 1.22 or newer plus GCC/Clang for cgo; C++ needs a C++20 compiler. On Ubuntu, build-essential supplies the C/C++ compiler and linker. Python clients need no C/C++ compiler. The OS needs its normal CA certificate package for HTTPS.
git clone https://github.com/babelfornet/native-licensing-examples.git
cd native-licensing-examples
python3 scripts/setup-sdk.py
python3 scripts/run-example.py pythonThe shared runner builds the selected client and runs it from the repository root. It does not build the SDK or download dependencies. A successful offline run prints License verified. Feature Pro enabled. The equivalent direct command is:
mkdir -p .build
python3 python/main.pyThe Rust build script and Go cgo directives set the runtime path on Linux. C++ explicitly supplies -L for link-time lookup and -Wl,-rpath for runtime lookup. Python selects .so in runtime_path(). If you deploy a compiled client elsewhere, ship the single .so in your application layout and configure an appropriate runtime path, such as $ORIGIN/../lib for a sibling lib/ directory.
Windows: install, build and run
Use x64 Python 3.9 or later. For C++ or Rust/MSVC, install Visual Studio’s Desktop development with C++ workload and open an x64 Native Tools terminal. Rust uses the x86_64-pc-windows-msvc toolchain. Go/cgo additionally needs a MinGW-compatible GCC or Clang toolchain, such as MSYS2 UCRT64 or LLVM-MinGW; cl.exe is not Go’s cgo C compiler.
From PowerShell in the example checkout:
python scripts/setup-sdk.py
python scripts/run-example.py pythonThe Python runner makes .sdk/bin available to the child process through its PATH. The C++ runner uses cl when available, Rust links the MSVC import .lib, and Go links the C ABI with its cgo compiler. To build and run directly:
New-Item -ItemType Directory -Force .build | Out-Null
$env:PATH = "$PWD\.sdk\bin;$env:PATH"
python python/main.pyFor deployment, copy BabelLicensingNative.dll next to the executable, or put its directory on the application’s controlled DLL search path. Python loads its explicit absolute DLL path through ctypes.CDLL, using the C calling convention. Do not copy a Linux .so or macOS .dylib onto Windows, and do not distribute the MSVC import .lib as a runtime dependency.
The numbered sections below show the macOS reference build and explain the shared integration code. Offline, activation and floating logic is identical on all three systems; the loader and linker settings above account for the platform differences.
1. Check Python and the process architecture
Install Python 3. The client example uses only the standard library; you do not need a Babel Python wheel, pip install ctypes, a .NET runtime or a C++ compiler to run it.
python3 --version
python3 -c 'import platform, struct; print(platform.machine(), struct.calcsize("P") * 8)'
uname -mUse a 64-bit arm64 Python process with the arm64 SDK. An Intel Python process running under Rosetta cannot load the supplied arm64 .dylib.
Python executes the application code. The licensing engine is the compiled native library loaded by ctypes; compiling Python bytecode alone does not produce a standalone native executable.
2. Install the example and runtime
git clone https://github.com/babelfornet/native-licensing-examples.git
cd native-licensing-examples
python3 scripts/setup-sdk.pyRun python3 scripts/setup-sdk.py to download the pinned platform archive. To use a previously downloaded ZIP, add --archive /absolute/path/to/sdk.zip. Both paths verify SHA-256.
The Python app uses:
native-licensing-examples/
├── .sdk/lib/
│ └── libBabelLicensingNative.dylib
├── demo/
│ ├── perpetual.xml
│ ├── trial.xml
│ └── publisher-key.txt
└── python/main.pyDistribute only libBabelLicensingNative.dylib as the SDK runtime. The required Botan modules are linked statically into it; users do not install or ship Botan or SQLite. The SDK still uses macOS system libraries.
Platform library selection
The loader uses an explicit path derived from the application directory:
import platform
def runtime_path():
names = {
'Darwin': 'lib/libBabelLicensingNative.dylib',
'Linux': 'lib/libBabelLicensingNative.so',
'Windows': 'bin/BabelLicensingNative.dll',
}
return ROOT / '.sdk' / names[platform.system()]The example defines ROOT from Path(__file__).resolve().parents[1]. ctypes.CDLL(str(runtime_path())) loads the matching platform library using the C calling convention.
3. Load the shared library by its application path
The script locates the repository root relative to its own file:
import ctypes as c
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
lib = c.CDLL(str(runtime_path()))This uses an explicit library path rather than relying on the shell’s search path. ctypes.CDLL asks the operating system to load the binary and its dependencies; it does not compile the library.
Unlike the other examples’ relative default input paths, the Python script also resolves its default demo files under ROOT. You can therefore start the script from a different working directory without changing which default license it opens. Explicit relative paths supplied on the command line are still relative to the process working directory.
4. Declare argument and return types
The library’s functions return status integers and write an opaque handle through an output argument. Configure those types before calling them:
lib.babel_abi_version.argtypes = []
lib.babel_abi_version.restype = c.c_int
lib.babel_license_load.argtypes = [c.c_char_p, c.POINTER(c.c_void_p)]
lib.babel_license_load.restype = c.c_int
lib.babel_license_verify.argtypes = [c.c_void_p, c.c_char_p, c.c_char_p]
lib.babel_license_verify.restype = c.c_int
lib.babel_license_validate.argtypes = [c.c_void_p]
lib.babel_license_validate.restype = c.c_int
lib.babel_license_feature_enabled.argtypes = [c.c_void_p, c.c_char_p]
lib.babel_license_feature_enabled.restype = c.c_int
lib.babel_license_free.argtypes = [c.c_void_p]
lib.babel_license_free.restype = NoneThe full example relies on ctypes’ default c_int return type for the status functions; the explicit declarations above are equivalent and make the contract visible when integrating into another application.
| Python declaration | Native meaning |
|---|---|
c.c_char_p | NUL-terminated bytes, UTF-8 for text arguments. |
c.c_void_p | Opaque native handle, preserving the full pointer width. |
c.POINTER(c.c_void_p) | Address of the slot in which load writes its handle. |
c.c_int | Status code or integer boolean result. |
restype = None | A function that returns void. |
Do not declare babel_license_load as returning a pointer: its return value is a status. Do not store the handle in c_int, which can truncate a pointer. The authoritative signatures are in the public C header .
5. Read bounded byte strings
The source uses this helper:
def read_cstring(path, maximum):
with open(path, 'rb') as stream:
data = stream.read(maximum + 1)
if len(data) > maximum or b'\0' in data:
raise ValueError(f'Invalid or oversized input: {path}')
return dataThe license limit is 1 MiB; the key limit is 16 KiB. Reading one extra byte detects an oversized file without reading it in full. Embedded NUL is rejected because the C API expects NUL-terminated input; otherwise the native function could see a shorter value than the Python program intended.
License and key file contents remain bytes. The textual algorithm and feature arguments use .encode() to create UTF-8 bytes. The example keeps those input objects alive while making the corresponding calls.
6. Verify, validate and release the handle
Check the ABI before loading data:
if lib.babel_abi_version() != 2:
raise ValueError('This example requires native ABI v2')Then load the file and put every operation on the returned handle inside try/finally:
handle = c.c_void_p()
status = lib.babel_license_load(text, c.byref(handle))
if status:
raise ValueError(f'License parsing failed (status {status})')
try:
status = lib.babel_license_verify(handle, b'RSA', key)
if status:
raise ValueError(f'Publisher signature verification failed (status {status})')
status = lib.babel_license_validate(handle)
if status:
raise ValueError(f'Runtime license validation failed (status {status})')
if lib.babel_license_feature_enabled(handle, b'Pro') != 1:
raise ValueError('Feature Pro is absent or expired')
print('License verified. Feature Pro enabled.')
finally:
lib.babel_license_free(handle)c.byref(handle) supplies the output slot. The finally block releases the native object even if signature verification or a later check raises an exception. Only a successful load owns a handle to release.
Parsing is not authentication. verify requires the publisher’s trusted public key; validate checks absolute expiry and refuses unsupported restrictions. The feature query also checks feature expiry. Diagnostic getters are not authorization decisions.
Python’s garbage collector does not own this handle. Do not replace the library deallocator with Python memory management, and do not release the same handle twice. The example keeps each handle within one operation rather than sharing it between threads.
7. Run the application
From the repository root:
python3 python/main.pyExpected output:
License verified. Feature Pro enabled../run.sh python launches the same script. A virtual environment is optional because there are no third-party Python dependencies:
python3 -m venv .venv
.venv/bin/python python/main.pyTo check bytecode compilation separately:
python3 -m py_compile python/main.pyThis checks that Python can compile the source. It neither embeds the native runtime nor turns the application into a standalone executable. The runnable result is the Python program plus its runtime files.
8. Try trial and customer licenses
python3 python/main.py demo/trial.xml
python3 python/main.py demo/perpetual.xml
python3 python/main.py \
/absolute/path/customer.xml /absolute/path/publisher-key.txt RSA Pro
python3 python/main.py demo/expired.xml
python3 python/main.py demo/no-feature.xml
python3 python/main.py demo/restricted.xmlThe arguments are [license.xml [publisher-key.txt [RSA|ECDSA [feature]]]]. The default feature is Pro and the default algorithm is RSA.
The expired file fails with status 10. An unsupported restriction fails with status 4. Missing or expired features also cause a nonzero process exit status. The top-level handler prints the error to stderr and exits with 1; it does not continue into the protected feature.
The supplied key and licenses belong to this demonstration application. For your own files, use your own publisher’s public key. Keep it pinned in the application or trusted configuration rather than selecting it from the license document. The private key is used only by the publisher; see Issue your own files.
9. Assemble a runnable application directory
You can test a separate application directory without creating a Python installer:
mkdir -p .build/python-app/python
cp python/main.py python/online.py .build/python-app/python/
cp -R .sdk .build/python-app/.sdk
cp -R demo .build/python-app/demo
python3 .build/python-app/python/main.pyThese copy commands assume .build/python-app is a new directory. The copy includes the SDK’s existing license and dependency notices. The script’s ROOT now resolves to .build/python-app, so it finds that directory’s own runtime and demo files.
This directory still needs a compatible Python interpreter and operating system. Building a standalone Python executable is a separate packaging step; it is not required to integrate with the native C ABI.
Troubleshooting
| Error | Check |
|---|---|
OSError while opening the library | .sdk/lib/libBabelLicensingNative.dylib exists and matches the application architecture. |
| Incompatible architecture | The Python process and library are both arm64. |
| ABI version error | The loaded SDK implements ABI v2. |
ctypes.ArgumentError | Function argtypes, bytes conversions and handle declarations match the header. |
Status 3 | The public key belongs to the signer and no signed content was edited. |
Status 10 or 4 | The file expired or contains an unsupported restriction; do not enable the feature. |
Run python3 scripts/test-examples.py from the repository root to check all four languages against the supplied positive and negative files.
Run activation and floating licensing
The online modes use the same application and the same single SDK runtime as the offline example. They need a service endpoint, a customer user key and a publisher public key pinned by your application. Do not supply the license used to evaluate or purchase Babel Licensing itself: these are licenses for your own application.
For a self-contained local protocol demonstration, start the fixture in a separate terminal from the repository root:
python3 scripts/demo-service.pyThe fixture listens only on 127.0.0.1:8765, reads signed demo payloads, and allocates one disposable floating seat. It needs Python 3, but no .NET runtime, database or signing private key. It is a protocol fixture, not a deployable Licensing Service.
In your application terminal, run:
export BABEL_SERVICE_URL=http://127.0.0.1:8765/
export BABEL_ALLOW_LOOPBACK_HTTP=1
export BABEL_MACHINE_ID=native-example-machine
export BABEL_PUBLISHER_KEY_FILE="$PWD/demo/publisher-key.txt"
export BABEL_ALGORITHM=RSA
export BABEL_CLIENT_ID=native-example-python
export BABEL_USER_KEY=demo-activation
./run.sh python --online activation
export BABEL_USER_KEY=demo-floating
./run.sh python --online floatingActivation prints a verified grant, validates it again with the service, then deactivates it. Floating prints a verified grant, renews it twice, then releases its seat. Both exit with an error when acquisition, signature verification, machine validation, feature entitlement or a network operation fails.
Use your actual Licensing Service
Configure an activation or floating license for your application in Babel Licensing Service, with a supported RSA or ECDSA P-256 publisher key and a Pro feature (or adapt the feature name). The service must return a valid signed XML or compact license. Stateful trial run counts/days-from-first-use, domain, usage and custom restrictions remain unsupported; an absolute expiry is supported.
unset BABEL_ALLOW_LOOPBACK_HTTP BABEL_MACHINE_ID
export BABEL_SERVICE_URL=https://licensing.example.com/
export BABEL_PUBLISHER_KEY_FILE=/absolute/path/your-publisher-public-key.txt
export BABEL_ALGORITHM=RSA
# Supply BABEL_USER_KEY from your application's secure configuration.
./run.sh python --online activation
# Or use a floating user key and --online floating.Replace the endpoint with your own service root. The SDK appends v1/auth/login and v1/license/*. It authenticates with the user key, then uses a bearer token for service requests. Credentials are read from the environment in these examples and are not printed or passed on the command line. Production applications should use their own secure configuration mechanism.
With BABEL_MACHINE_ID unset, the example calls babel_hardware_key. Keep the machine identity and BABEL_CLIENT_ID stable for an activation; choose distinct client identities for concurrently running floating clients. The fixed machine ID above exists only to match the signed local fixture. Pin the public key in your application or trusted configuration; never trust the key supplied inside a server response.
HTTPS always verifies certificates and hostnames; redirects are not followed. Plain HTTP requires the explicit loopback flag and cannot be used with a remote hostname. The sample timeout is 15 seconds per HTTP request; authentication and the following service request each have their own bound.
Integration code and lifecycle
# ctypes declarations and cleanup are in python/online.py.
success(lib.babel_online_activate(handle, user_key), 'Activate')
success(lib.babel_online_check(handle), 'Check before work')
if lib.babel_online_feature_enabled(handle, b'Pro') != 1:
raise ValueError('Pro is not licensed')
success(lib.babel_online_validate_activation(handle), 'Validate with service')
success(lib.babel_online_deactivate(handle), 'Deactivate')babel_online_create.argtypes declares six UTF-8 string pointers, a timeout, flags and an output handle pointer. Every call takes a c_void_p session handle. Declare babel_online_heartbeat_after_ms.restype = c.c_int64; its return value is a signed 64-bit millisecond interval, and -1 means the lease is unavailable. finally runs release/deactivation when necessary, then frees the handle.
For floating, python/online.py calls babel_online_request_floating, sleeps for the returned delay using time.sleep(delay / 1000), then calls babel_online_heartbeat and babel_online_check. It repeats twice before babel_online_release. Python does not implement cryptography or make HTTP calls itself.
When staging an application outside this checkout, copy both python/main.py and python/online.py into its python/ directory, together with the single SDK runtime and required configuration. Python runs the source directly; no C++ compiler is needed on the user’s machine.
The session API retains the verified license and floating token internally. Use babel_online_check immediately before protected work and babel_online_feature_enabled for feature access. Do not extract or cache a Boolean from the initial acquisition and use it indefinitely.
For a running floating application, schedule heartbeats before the returned deadline throughout the protected session. The two-heartbeat loop is only the executable demonstration. Stop protected work on any heartbeat/check failure. There is no background renewal, offline grace or persistent activation cache in this version. A failed heartbeat invalidates the local grant immediately; a lease cannot be revived after its deadline and must be acquired again.
Release floating seats or deactivate activations explicitly when the application workflow requires it. The examples do this when they exit so repeated evaluations do not consume seats. A product can retain an activation between runs by keeping stable identities and activating/validating it again at startup; this SDK does not save a signed license or token to disk. Freeing a handle releases memory and performs no network request.
If release/deactivation fails, local use is still disabled. Retry cleanup with the same live handle while it exists; a floating token eventually expires at the service. Handle destruction, process termination and network failure do not prove that the server has received cleanup.
Online refusal scenarios
| Status | Meaning and application action |
|---|---|
2 / 3 | Malformed response/license or bad publisher signature: refuse protected work. |
4 | Unsupported algorithm, curve or restriction: adjust the issuing template, never bypass the check. |
10 | Signed license expired: obtain a valid application license. |
11 | Network, TLS, timeout or response-size failure: stop protected work and report the failure. |
12 / 13 / 14 | Credentials rejected, HTTP/service failure or grant refused: check service policy and seat availability. |
15 | No active grant, released/expired lease or failed heartbeat: stop use and acquire a new lease as appropriate. |
16 | Signed hardware binding does not match the configured machine: refuse activation. |
Run python3 scripts/test-online-examples.py to build/run all four languages against local activation, floating renewal/release, denied grant, hardware mismatch and unsupported-restriction cases. The native SDK separately tests malformed JSON, tampered signed payloads, oversized responses, timeout, redirects, invalid TLS and lease loss. These protocol-fixture checks do not replace acceptance tests against your deployed Licensing Service.
Online commands in Windows PowerShell
The service and user key settings have the same meaning on every OS. For the supplied loopback fixture, open one terminal for python scripts/demo-service.py, then use another:
$env:BABEL_SERVICE_URL = "http://127.0.0.1:8765/"
$env:BABEL_ALLOW_LOOPBACK_HTTP = "1"
$env:BABEL_MACHINE_ID = "native-example-machine"
$env:BABEL_PUBLISHER_KEY_FILE = "$PWD\demo\publisher-key.txt"
$env:BABEL_ALGORITHM = "RSA"
$env:BABEL_CLIENT_ID = "native-example-python"
$env:BABEL_USER_KEY = "demo-activation"
python scripts/run-example.py python --online activation
$env:BABEL_USER_KEY = "demo-floating"
python scripts/run-example.py python --online floatingFor your actual HTTPS Licensing Service, remove the loopback flag and fixed demo machine identity, then supply your service root, publisher key and application user key. Run the same --online commands. Linux uses the shell export commands in the online section and can use python3 scripts/run-example.py instead of ./run.sh.
Remove-Item Env:BABEL_ALLOW_LOOPBACK_HTTP -ErrorAction SilentlyContinue
Remove-Item Env:BABEL_MACHINE_ID -ErrorAction SilentlyContinue
$env:BABEL_SERVICE_URL = "https://your-licensing-service.example/"
$env:BABEL_PUBLISHER_KEY_FILE = "C:\YourApplication\publisher-public-key.txt"
# Supply BABEL_USER_KEY through your application's secure configuration.
python scripts/run-example.py python --online activationRun python scripts/test-examples.py and python scripts/test-online-examples.py on Windows (or python3 on Linux/macOS) to exercise the same accepted/refused paths in all four bindings.