SDK-Integration
Python-SDK-Integration
Fügen Sie Keyright-Lizenzierung zu einer Python-App hinzu: das Paket keyright installieren, Lizenzen offline prüfen, online aktivieren, Funktionen über Entitlements schützen sowie Testversionen und netzwerkisolierte (air-gapped) Maschinen behandeln — konstruktionsbedingt fail-closed.
Dies ist der Rundgang für den Python-Client — von pip install bis zu einer ausgelieferten App, die bezahlte Funktionen gegen einen echten Lizenzschlüssel freischaltet. Das Paket keyright prüft exakt dieselben Lizenz- und Lease-Formate wie das .NET-SDK, sodass eine mehrsprachige Produktlinie einen Keyright-Mandanten und einen öffentlichen Schlüssel teilen kann. Diese Seite legt ihren Schwerpunkt auf den Python-Client-Code; die Codeblöcke verwenden die echte SDK-Oberfläche, kopieren Sie sie also unverändert.
Bevor Sie beginnen: die Anbietereinrichtung
Die einmaligen Anbieterschritte sind sprachunabhängig und werden an anderer Stelle vollständig behandelt — erledigen Sie sie einmal und kommen Sie dann für den Client hierher zurück:
- Legen Sie Ihr Produkt an und notieren Sie seinen Slug (die Kennung, die Ihre App als
productsendet). - Kopieren Sie Ihren öffentlichen Signaturschlüssel — einen base64-kodierten
SubjectPublicKeyInfo-String. Er ist kein Geheimnis; er wird in Ihrer App ausgeliefert und kann Signaturen nur prüfen, nie erzeugen. Die gesamte Sicherheit beruht darauf, dass der private Schlüssel verschlüsselt auf dem Server bleibt. - Definieren Sie Tiers und Entitlements — jeder Tier trägt eine Platzanzahl und eine Vorlage benannter Flags und numerischer Limits, die in jede Lizenz eingebacken werden.
Schritt für Schritt, mit den Dashboard-Bildschirmen und den entsprechenden API-Aufrufen, finden Sie das in Erste Schritte und im .NET-Rundgang. Alles Folgende setzt voraus, dass Sie Ihren Produkt-Slug und Ihren base64-kodierten öffentlichen Schlüssel zur Hand haben.
So funktioniert es
Sie stellen Lizenzschlüssel serverseitig in Keyright aus; jeder wird mit dem privaten RSA-Schlüssel Ihres Mandanten signiert, der den Server nie verlässt. Ihre Python-App bettet nur den passenden öffentlichen Schlüssel ein und prüft Lizenzen offline dagegen, sodass eine routinemäßige Lizenzprüfung kein Netzwerk braucht. Für Platzdurchsetzung und Widerruf aktivieren Sie zusätzlich online: Die App tauscht einen Lizenzschlüssel gegen einen kurzlebigen signierten Lease, der an die Maschine gebunden ist, und arbeitet dann offline weiter, bis dieser Lease abläuft. Alles, was nicht stimmt — ungültige Signatur, falsches Produkt, Ablauf, Widerruf, eine zurückgestellte Uhr — löst zur Edition Free auf, statt eine Ausnahme auszulösen. Es ist fail-closed.
SDK installieren
Das Paket ist auf PyPI verfügbar. Es benötigt Python 3.8+ und zieht cryptography für die RSA-Prüfung nach:
pip install "keyright>=1.1.4"
Alles, was Sie brauchen, wird aus dem Top-Level-Paket exportiert:
from keyright import (
KeyrightClient,
KeyrightOptions,
LicenseInfo,
LicenseStatus,
Edition,
MachineFingerprint,
)
Client initialisieren
Erzeugen Sie beim Start einen KeyrightClient mit der statischen Factory initialize. Sie validiert die Optionen und lädt den öffentlichen Schlüssel vorab (und löst ValueError aus, wenn der Schlüssel fehlt); erzeugen Sie ihn also einmal und verwenden Sie ihn wieder:
from keyright import KeyrightClient, KeyrightOptions
license = KeyrightClient.initialize(KeyrightOptions(
product="acme-app", # muss mit dem Produkt-Slug übereinstimmen, für den Sie Schlüssel ausstellen
public_key_base64="MIIBIjANBgkq...", # der base64-kodierte öffentliche Schlüssel aus Ihrem Dashboard
service_url="https://keyright.delta1labs.com", # weglassen, wenn Sie nur Offline-Lizenzdateien ausliefern
))
Nur product und public_key_base64 sind sinnvoll zu konfigurieren; service_url ist nur für die Online-Aktivierung erforderlich. KeyrightOptions akzeptiert mehrere optionale Schlüsselwortargumente, die steuern, woher eine Lizenz gelesen und wie sie validiert wird:
KeyrightOptions(
product="acme-app",
public_key_base64="MIIBIjANBgkq...",
service_url="https://keyright.delta1labs.com",
# Explizite Lizenzquellen (höchste Rangfolge zuerst)
license_string=None, # ein direkt übergebener Lizenz-JSON-String
license_file_path=None, # ein Pfad zu einer Lizenzdatei
env_var_name="KEYRIGHT_LICENSE", # Umgebungsvariable, die einen Lizenzdateipfad benennt (das ist der Standard)
# Einen alten Schlüssel während eines Rotationsfensters gültig halten
additional_public_keys_base64=["<previous public key>"],
# Offline-Widerruf — eine signierte Widerrufsliste mit Ihrem Build ausliefern
revocation_list_json=None,
revocation_list_path=None,
# Node-Lock-Toleranz: wie viele Änderungen weicher Komponenten erlaubt sind (Standard 1)
node_lock_tolerance=1,
))
Das SDK sucht eine Lizenz in mehreren Quellen, in Rangfolge (höchste zuerst): ein explizites license_string, dann ein explizites license_file_path, dann die von der Umgebungsvariable KEYRIGHT_LICENSE benannte Datei, dann config_license_path, dann der App-Daten-Pfad des Betriebssystems ({LocalAppData}/Keyright/{product}/license.json) und schließlich der zwischengespeicherte Aktivierungs-Lease. Normalerweise setzen Sie keine davon — die Online-Aktivierung schreibt den Lease-Cache für Sie.
Funktionen offline schützen
Rufen Sie validate() auf. Es ermittelt die beste Lizenz aus den obigen Quellen und prüft RSA-Signatur, Produktübereinstimmung, Node-Lock, Ablauf, eine etwaige mitgelieferte Widerrufsliste sowie den Testversions-/Uhrmanipulationsstatus — alles offline — und es löst nie eine Ausnahme aus. Bei jedem Fehler liefert es eine LicenseInfo in der Edition Free, die den Grund enthält.
validate() liefert ein Tupel (LicenseInfo, source) — entpacken Sie es immer. source ist ein kurzer Diagnose-String ("lease", "environment", "default", "none", …), der Ihnen sagt, welche Quelle gewonnen hat:
from keyright import Edition
info, source = license.validate() # -> (LicenseInfo, str) — das Tupel entpacken
if info.is_paid: # True für jede Edition oberhalb von Free
enable_paid_features()
if info.edition == Edition.ENTERPRISE:
enable_enterprise_features()
Wenn Sie nur die LicenseInfo möchten und es Ihnen egal ist, aus welcher Quelle sie stammt, liefert validate_info() nur das erste Element:
info = license.validate_info() # entspricht license.validate()[0]
Über Entitlements schützen
Schützen Sie einzelne Funktionen bevorzugt über Entitlements statt über die Edition, damit eine Änderung der Tier-Vorlage nicht bedeutet, neuen Code auszuliefern. LicenseInfo.entitlements ist ein EntitlementSet mit zwei typisierten, fail-closed Zugriffsmethoden:
info = license.validate_info()
# Boolesches Flag — ein fehlendes oder nicht wahrheitsgemäßes Flag gilt als False
if info.entitlements.is_enabled("export"):
show_export_command()
# Numerisches Limit — den fail-closed-Fallback geben Sie selbst an; ein fehlender oder
# nicht parsbarer Wert liefert Ihren Fallback, und "unlimited" liefert einen sehr großen int
max_projects = info.entitlements.get_limit("max-projects", fallback=1)
if current_project_count >= max_projects:
prompt_to_upgrade()
Für eine einzelne schnelle Prüfung stellt der Client außerdem Komfort-Abkürzungen bereit, die an Ort und Stelle validieren — license.is_enabled("export"), license.get_limit("max-projects", 1) und license.edition(). Jede davon führt intern validate() erneut aus; wenn Sie also mehrere Entitlements auf einmal prüfen, validieren Sie einmal und verwenden Sie das EntitlementSet wieder:
info = license.validate_info()
ents = info.entitlements
can_export = ents.is_enabled("export")
max_seats = ents.get_limit("max-seats", 1)
Sowohl is_enabled als auch get_limit sind fail-closed: Ein fehlendes Flag gilt als deaktiviert, ein fehlendes oder nicht parsbares Limit liefert Ihren Fallback. Kombinieren Sie eine Editionsprüfung mit einer Entitlement-Prüfung, wenn eine Funktion zu einem Tier und zu einem Vorlagen-Flag gehört und beide bestehen müssen.
Status für Diagnosen lesen
Um herauszufinden, warum eine Prüfung so ausgefallen ist, wie sie ausgefallen ist — für einen „Registrieren“-Dialog oder fürs Logging —, lesen Sie die snake_case-Felder von LicenseInfo:
info, source = license.validate()
print(info.status) # LicenseStatus-Enum: VALID, NO_LICENSE, SIGNATURE_INVALID,
# EXPIRED, MACHINE_MISMATCH, REVOKED, CLOCK_TAMPERED, MALFORMED
print(info.message) # für Menschen gedachter Erklärungstext
print(info.licensee) # an wen die Lizenz ausgestellt wurde
print(info.edition) # Edition.FREE | LICENSED | ENTERPRISE
print(info.is_valid) # True nur, wenn status == LicenseStatus.VALID
print(info.is_paid) # True für jede Edition oberhalb von Free
print(info.status_badge) # z. B. "Enterprise", "Licensed Trial", "Free"
print(info.kind) # LicenseKind.PERPETUAL | SUBSCRIPTION | TRIAL
print(info.is_trial) # True für eine Testlizenz / einen Test-Lease
print(info.expiry_utc) # datetime | None (None = unbefristet)
print(info.days_remaining) # int | None — z. B. 27 für ein Badge „noch 27 Tage“
print(info.trial_days) # int | None — die konfigurierte Länge der Testversion
print(source) # welche Quelle sie ermittelt hat: "lease", "environment", ...
is_valid ist die strikte Prüfung (Status ist VALID); is_paid ist die Prüfung „bezahlte Funktionen freischalten“ (Edition oberhalb von Free). Eine gültige Testversion ist beides.
Online mit einem Lizenzschlüssel aktivieren
Wenn ein Kunde einen Schlüssel eingibt, rufen Sie activate(key) auf. Es ist synchron. Es sendet den Schlüssel plus eine stabile Maschinen-ID an Ihren Service, der einen Platz belegt und einen kurzlebigen, an diese Maschine gebundenen signierten Lease zurückgibt. Das SDK prüft den Lease gegen Ihren eingebetteten öffentlichen Schlüssel und speichert ihn lokal zwischen, sodass jedes spätere validate() ohne Netzwerk erfolgreich ist, bis die Kulanzfrist des Lease abläuft.
activate löst für die gewöhnlichen Fehlerpfade (ungültiger Schlüssel, Platzlimit, offline, widerrufen) keine Ausnahme aus — es liefert eine fail-closed LicenseInfo, genau wie validate(). Es liefert die LicenseInfo direkt (kein Tupel). Prüfen Sie das Ergebnis:
info = license.activate(customer_entered_key)
if info.is_valid and info.is_paid:
# Aktiviert. Der Lease ist zwischengespeichert; die App arbeitet jetzt offline, bis er abläuft.
show_licensed_ui(info.status_badge) # z. B. "Enterprise" oder "Enterprise Trial"
else:
# info.message anzeigen; die App bleibt im Modus Free.
show_activation_error(info.message) # "All seats for this license are in use." usw.
- Plätze werden serverseitig durchgesetzt. Das Aktivieren von mehr Maschinen, als die Lizenz erlaubt, liefert ein Platzlimit-Ergebnis (
info.status == LicenseStatus.NO_LICENSE, Meldung „All seats for this license are in use.“) und keinen Lease. Das erneute Aktivieren einer bereits gebundenen Maschine ist idempotent — es wird kein zusätzlicher Platz belegt. - Offline-Kulanz. Ist der Service nicht erreichbar, greift
activateauf einen noch gültigen zwischengespeicherten Lease zurück, sodass ein kurzer Ausfall den Benutzer nicht aussperrt. Nähert sich der Lease dem Ablauf, muss die App den Server erneut erreichen, um ihn zu aktualisieren. - Widerruf wird bei der nächsten Aktualisierung wirksam — siehe unten.
activate löst ValueError nur bei Programmierfehlern aus: eine fehlende service_url oder ein leerer Schlüssel.
Deaktivieren, um einen Platz freizugeben
Um den Platz dieser Maschine freizugeben, damit er anderswo erneut aktiviert werden kann, rufen Sie deactivate(key) auf. Es bindet dieselbe Maschinen-ID, die activate verwendet, und sendet sie an den Service. Anders als activate hat es keinen Offline-Fallback — ist der Service nicht erreichbar, wird der Fehler weitergereicht, also kapseln Sie den Aufruf:
try:
status = license.deactivate(license_key) # -> "ok" | "not_found"
if status == "ok":
# Der Platz ist freigegeben und der lokal zwischengespeicherte Lease wurde gelöscht.
show_free_ui()
except (RuntimeError, OSError) as ex:
show_error(f"Could not reach the licensing service: {ex}")
Bei "ok" löscht das SDK den lokal zwischengespeicherten Lease für dieses Produkt, sodass ein nachfolgendes validate() auf Free zurückfällt.
Netzwerkisolierte (air-gapped) Maschinen: Offline-Lease importieren
Für eine Maschine, die den Service nie erreichen kann, signiert ein Operator einen Offline-Lease für deren Maschinen-ID (Dashboard Licenses → offline lease oder POST /admin/licenses/{id}/offline-lease) und liefert das JSON per Datei aus. Um dem Operator die ID dieser Maschine zu übermitteln, lesen Sie sie lokal aus:
from keyright import MachineFingerprint
machine_id = MachineFingerprint.current().to_bound_string() # "KRM1:..." — diesen Wert dem Operator übergeben
print(machine_id)
Importieren Sie dann den gelieferten Lease. import_offline_lease prüft Signatur, Produkt, Maschine und Nicht-Ablauf über denselben Pfad, den ein zwischengespeicherter Lease verwendet, speichert ihn bei Erfolg lokal zwischen und löst ValueError aus, wenn der Lease ungültig ist — behandeln Sie das also:
try:
with open("acme.lease.json", encoding="utf-8") as f:
info = license.import_offline_lease(f.read())
# Zwischengespeichert. Nachfolgende validate()-Aufrufe sind jetzt offline erfolgreich.
show_licensed_ui(info.status_badge)
except ValueError as ex:
# falsche Signatur / falsches Produkt / falsche Maschine oder abgelaufen
show_error(str(ex))
Node-Locking
Das SDK leitet einen stabilen Maschinen-Fingerabdruck aus einer Ankerkomponente (der Maschinen-GUID) plus weichen Signalen (Hostname, Betriebssystem) ab. node_lock_tolerance (Standard 1) lässt eine kleine Hardware- oder Betriebssystemänderung durchgehen, ohne den Benutzer auszusperren, während der Anker immer übereinstimmen muss. Die Aktivierungsanfrage sendet diese Maschinen-ID, sodass Plätze pro Gerät gezählt werden. MachineFingerprint.current().to_bound_string() liefert exakt den String, an den der Server einen Lease bindet; MachineFingerprint.current().display_id liefert eine kurze, für Menschen lesbare Form KR-XXXX-XXXX für Support-Gespräche.
Testversionen
Es gibt keine SDK-Methode für Testversionen — ein Testschlüssel wird außerhalb des SDK durch Aufruf des öffentlichen Endpunkts POST /v1/trial bezogen (der den Schlüssel per E-Mail an den Kunden sendet) und dann genau wie ein bezahlter Schlüssel an activate() übergeben. Ein Testschlüssel wird über exakt denselben activate-Pfad aktiviert wie ein bezahlter. Zeigen Sie den Zustand direkt aus LicenseInfo an:
info = license.validate_info()
if info.is_trial:
badge = info.status_badge # z. B. "Enterprise Trial"
left = info.days_remaining # z. B. 27 -> „noch 27 Tage“
show_trial_banner(badge, left)
Die Dauer einer Testversion zählt ab der ersten Aktivierung auf dieser Maschine (das SDK hält dies lokal fest). Läuft sie ab, liefert validate() Free mit status == LicenseStatus.EXPIRED und einer Meldung, die die Länge der Testversion nennt. Eine Testversion wird nahtlos abgelöst, wenn der Kunde später einen bezahlten Schlüssel aktiviert. Vollständiger Ablauf: Kostenlose Self-Service-Testversionen.
Fehlerbehebung
- Die Aktivierung meldet, der Schlüssel sei nicht erkannt worden. Die häufigste Ursache ist eine Produktabweichung: Der Schlüssel wurde für ein anderes Produkt (oder einen anderen Mandanten) ausgestellt als der
product-Slug, mit dem Ihr Client konfiguriert ist. Der Server begrenzt die Aktivierung auf das Produkt, daher aktiviert ein gültiger Schlüssel füracme-appkeinen Client, der mitproduct="other-app"initialisiert wurde. Stellen Sie sicher, dass der Slug mit dem Produkt übereinstimmt, unter dem Sie den Schlüssel ausgestellt haben. - Alles wird als Free erkannt. Das ist Absicht — das SDK ist bei jedem Prüfproblem fail-closed, statt eine Ausnahme auszulösen. Lesen Sie
info.status(NO_LICENSE,SIGNATURE_INVALID,EXPIRED,MACHINE_MISMATCH,REVOKED,CLOCK_TAMPERED,MALFORMED) undinfo.message, um zu sehen, welches Problem vorliegt. EinSIGNATURE_INVALIDbedeutet fast immer, dass der eingebettetepublic_key_base64nicht zu dem Mandanten passt, der den Schlüssel signiert hat. validate()„liefert zwei Dinge“. Es liefert ein Tupel(LicenseInfo, source)— entpacken Sie es (info, source = license.validate()) oder rufen Sievalidate_info()auf, um nur dieLicenseInfozu erhalten.- Eine zeitlich begrenzte Lizenz lässt sich plötzlich nicht mehr validieren. Wird die Systemuhr bei einer Testversion oder einem Abonnement um mehr als die Toleranz (Standard 24 h) zurückgestellt, wertet das SDK dies als Uhrmanipulation und liefert den Status
CLOCK_TAMPERED. Das bleibt nicht haften — stellen Sie die korrekte Zeit ein, und die Validierung erholt sich beim nächsten Aufruf. Unbefristete Lizenzen unterliegen dieser Prüfung nie. ValueErrorausactivateoderdeactivate. Diese lösen nur bei Programmierfehlern eine Ausnahme aus — eine fehlendeservice_urloder ein leerer Schlüssel. Gewöhnliche Lizenzierungsfehler kommen alsLicenseInfozurück, nicht als Ausnahme.
Siehe auch
- Erste Schritte — der komprimierte Anbieterpfad und die Go-live-Checkliste.
- Aktivierungslebenszyklus — wie Schlüssel, Plätze und Leases ihre Zustände durchlaufen.
- Sicherheit & Leases — das Signaturmodell, das Lease-Format und die Fail-closed-Garantien.
- HTTP-API-Referenz — die vollständige Endpunktübersicht.
- Samples-Repository — lauffähige Client-Beispiele in allen Sprachen.