Skip to content

Python SDK

The Python 3.9+ package is dycrypt-license, imported as dycrypt_license. Its only runtime dependency is cryptography.

Until the package is published to PyPI, install it from the DYCRYPT SDK checkout:

Terminal window
pip install /path/to/dycrypt/sdk/python

The drop-in module at sdk/python/dropin/dycrypt_license.py exposes the same API.

import sys
from dycrypt_license import fingerprint
if "--fingerprint" in sys.argv:
print(fingerprint())
raise SystemExit(0)

Use this before issuing a Full Offline or offline-activated Hybrid licence.

from dycrypt_license import LicenseVerifier, OnlinePolicy
with open("product-public-key.pem", encoding="utf-8") as handle:
public_key_pem = handle.read()
verifier = LicenseVerifier(
public_key_pem,
"your-product-key",
issuer_url="https://app.dycrypt.com",
org_slug="your-org-slug",
online_policy=OnlinePolicy.ADVISORY,
timeout=5.0,
state_path="/var/lib/your-app/dycrypt-state.json",
)

Omit the issuer settings and state_path for Full Offline. Build the verifier once and share it safely between threads.

state_path preserves successful-check timestamps across application restarts. Protect that file with the same operating-system permissions as other application state.

with open("customer.lic", encoding="utf-8") as handle:
result = verifier.verify(handle.read())
enforce(result)
with open("customer.lic", encoding="utf-8") as handle:
result = verifier.check(handle.read())
enforce(result)

check() is blocking. Run it at startup and from the background schedule your application owns. The SDK performs local verification before any request is made.

from dycrypt_license import LicenseStatus
def enforce(result):
if result.status == LicenseStatus.ACTIVE:
return
if result.status == LicenseStatus.GRACE:
print("Licence is inside its expiry grace period")
return
if result.status == LicenseStatus.STALE:
raise SystemExit("Reconnect to refresh the licence status")
if result.status == LicenseStatus.PENDING:
raise SystemExit("Licence starts at %s" % result.starts_at)
raise SystemExit("Unlicensed (%s): %s" % (result.status, result.reason))

ACTIVE and GRACE are the only statuses where result.valid is true.

max_users = result.integer("maxUsers", 5)
edition = result.string("edition", "standard")
export_enabled = result.boolean("exportEnabled", False)
features = result.string_list("features", [])

Typed accessors never coerce a wrong JSON type. They return the fallback you provide.