Licensing for Python apps,
without the server.
RSA-signed licenses bound to each machine, tamper-evident local state in one SQLite file, trials that survive clock rollback — and a CLI that answers support tickets for you. Works compiled (Nuitka / PyInstaller), works offline.
Everything a licensing SDK should have shipped.
py-Rizmi covers the whole lifecycle: keys → issue → activate → enforce → revoke → diagnose. No accounts, no cloud dependency, no per-seat fees.
🔑Signed & hardware-bound
RS256 JWT licenses bound to an OS-level machine ID. A license issued for machine A fails on machine B with a precise error code.
🗄️One SQLite file
Keys, licenses, trial state and clock marks live in a single per-app database. Every row is HMAC-verified against a machine+app-bound key before use — editing the data is always detected.
⏱️Anti-rollback clock guard
A persisted high-water mark catches "wind the clock back and keep the trial". Redundant copies mean deleting one file (or the whole DB) resets nothing.
🎁Trial periods built in
The app issues itself a self-signed trial on first run. Deleting the trial restarts nothing; buying a license supersedes the trial mid-evaluation.
🧩3-line integration
LicenseGate wires validation, trial, activation and watchdog behind one object with truthy statuses your UI can render directly.
📦Compiled-app ready
Tested end-to-end inside Nuitka standalone binaries. Paths never derive from __file__; constants compile into C where patchers can't reach casually.
🚫Revocation lists
Publish a signed list of revoked license IDs; validators reject revoked installs even fully offline. Bad signatures are rejected loudly, never silently ignored.
🩺rizmi doctor
Machine ID readable? DB intact? Clock sane? Key pinned correctly? One command prints the checklist your support team was going to ask for anyway.
🖥️In-app activation UI
Your app hosts the paste box or file picker; py-rizmi runs the full validation chain before anything is stored. Two methods: token or file.
Licensing in three lines
After rizmi init MyApp generates your keys and snippet:
from py_rizmi import LicenseGate
gate = LicenseGate(app_name="MyApp", public_key=KEY,
expected_fingerprint=FINGERPRINT, config_dir=cfg)
status = gate.start() # first run starts the trial automatically
if not status:
print(status.message) # expired / tampered / wrong machine…
Full integration guide →