Skip to main content

Official Python SDK for SublimeKeys — license activation and verification with offline-capable signed leases

Project description

sublimekeys

Official Python SDK for SublimeKeys — license key generation, activation, verification and trials for indie desktop apps, with offline-capable verification: once a license is activated, your app can verify it locally in milliseconds with no network call, for up to 7 days at a time, before quietly re-syncing online.

pip install sublimekeys

Quickstart

This mirrors the lifecycle described in the SublimeKeys docs: first run activates, every later run verifies (offline-first), uninstall deactivates.

from sublimekeys import SublimeKeysClient

client = SublimeKeysClient(product_id="my-app")

# First run — ask the user for their key once.
result = client.activate(license_key=user_entered_key)
if result.valid:
    unlock_full_version()

# Every later launch — offline-first, instant, falls back online
# automatically once the cached lease's 7-day trust window lapses.
result = client.verify(license_key=saved_key)
if result.valid:
    unlock_full_version()
print(result.source)  # "offline_cache" most days, "online" roughly weekly

# Uninstall / sign out.
client.deactivate(license_key=saved_key)

client.get_machine_id() gives you a stable, locally-persisted identifier if you need to store it yourself — every method above also generates and caches one automatically the first time it's needed, so passing it explicitly is optional.

Trials

result = client.start_trial()       # get-or-create a 7-day trial; idempotent —
                                     # reinstalling never resets the clock
if result.status == "active":
    print(f"{result.days_left} days left")

result = client.trial_status()      # read-only check, never starts one

Trial state lives server-side, keyed to machine_id — the server's clock decides days_left, never the client's, so rolling back a machine's system clock doesn't extend a trial. trial_status() still works offline: if the network call fails, it replays the last server-confirmed snapshot (source="offline_cache") instead of just failing — but that snapshot is never locally recomputed or decremented, so it can go stale (frozen at its last known value) rather than silently trusting the local clock. Falls back to status="network_error" only if there's no cached snapshot yet either (e.g. the very first check happens offline).

How offline verification works

When activate() or verify() succeeds, the server returns a lease — a small Ed25519-signed token proving "this license is valid for this machine, as of now." The SDK caches it locally and, on every later verify() call, checks the signature against a pinned public key built into this package — no network call, no dependency on the server being reachable.

The lease itself expires after 7 days (server-controlled). Once it does, the next verify() call transparently goes online, gets a fresh lease, and the cycle repeats. This means a revoked or expired license can take up to 7 days to be caught while a machine stays fully offline — an intentional tradeoff for instant, network-independent checks the rest of the time, not a bug.

API

Method What it does
activate(license_key, machine_id=None) First run. Always online.
verify(license_key, machine_id=None, allow_offline=True) Every later run. Offline-first by default.
deactivate(license_key, machine_id=None) Uninstall/sign-out. Frees the seat, clears the local cache.
start_trial(machine_id=None) Get-or-create a trial.
trial_status(machine_id=None) Read-only trial check.
get_machine_id() Stable per-install identifier (auto-generated, persisted locally).

All methods return a small dataclass (LicenseResult or TrialResult) — never raise for a normal "not valid" outcome. Network failures are retried automatically with exponential backoff (2 retries by default, tunable via SublimeKeysClient(..., max_retries=, backoff_base=)) before giving up; verify() then falls back to the offline cache or a valid=False, source="offline_cache_miss" result rather than raising. A 5xx response that survives all retries raises ServerError (a NetworkError subclass) instead of the usual dataclass return — the one case worth distinguishing from an ordinary "not valid" result.

Don't treat every valid=False as "bad license". A network failure on activate() or deactivate() (offline on first run, DNS hiccup, etc.) still returns a normal dataclass rather than raising — check source == "network_error" to show "no connection, try again" instead of "invalid license". verify() and the trial methods don't need this check on their own: they already try their local cache first, so a network failure there only surfaces as source == "offline_cache_miss" (verify()) or status == "network_error" with no source == "offline_cache" fallback available (trials, first-ever offline check only) once there's truly nothing usable, cached or online.

Command-line interface

Installing the package also installs a sublimekeys command — handy for testing an integration from a terminal or in a shell script, without writing a throwaway Python file:

sublimekeys activate --product my-app YOUR-LICENSE-KEY
sublimekeys verify --product my-app YOUR-LICENSE-KEY
sublimekeys verify --product my-app --json YOUR-LICENSE-KEY   # machine-readable
sublimekeys trial-status --product my-app
sublimekeys machine-id --product my-app

Exit code is 0 for a valid/active result, 1 for invalid, 2 for a usage error. Run sublimekeys --help or sublimekeys <command> --help for the full flag list (--base-url, --cache-dir, --machine-id, --json).

Packaging with PyInstaller

cryptography's compiled backend is the kind of dependency that works fine under python app.py and only breaks in a frozen build. Before shipping:

  • pyinstaller-hooks-contrib ships an official hook-cryptography and is usually auto-discovered. If you hit ModuleNotFoundError: _cffi_backend, add --collect-all cryptography (or --hidden-import=_cffi_backend) to your PyInstaller command.
  • Test the frozen executable's activate()/verify() calls, not just the unfrozen dev script — this is the single highest-value check before shipping to real users.
  • On macOS, notarizing a PyInstaller bundle that includes OpenSSL/rust-openssl dylibs is a known extra step — sign all bundled dylibs and test the signed, notarized build specifically.

See examples/pyinstaller/ for a minimal buildable example.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sublimekeys-0.3.0.tar.gz (17.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sublimekeys-0.3.0-py3-none-any.whl (15.2 kB view details)

Uploaded Python 3

File details

Details for the file sublimekeys-0.3.0.tar.gz.

File metadata

  • Download URL: sublimekeys-0.3.0.tar.gz
  • Upload date:
  • Size: 17.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for sublimekeys-0.3.0.tar.gz
Algorithm Hash digest
SHA256 f0a059df5fd617a503e76336370e74af48eaf13ec8deb5065a4434ac0b028948
MD5 3f52a937cf7d01d481a0ee76c952fb4e
BLAKE2b-256 d1eea16ca78adedf8af5af90e372ff641b9bb91ae3ee0507cb5ce169321926b8

See more details on using hashes here.

Provenance

The following attestation bundles were made for sublimekeys-0.3.0.tar.gz:

Publisher: publish.yml on Volume00/sublimekeys-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sublimekeys-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: sublimekeys-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 15.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for sublimekeys-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5a7fc0b918790cfc3f707863548ffc3c3d57c7d665c83a4c3feecb62847ce05a
MD5 e9bc2a19d6269eb477a41312821bb161
BLAKE2b-256 94c9c2313860b5eb58bbf3307870f669a8f850874fcb79f67a9669dcc0538e26

See more details on using hashes here.

Provenance

The following attestation bundles were made for sublimekeys-0.3.0-py3-none-any.whl:

Publisher: publish.yml on Volume00/sublimekeys-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page