Skip to content

Intégration du SDK

Intégration du SDK Python

Ajoutez les licences Keyright à une application Python : installez le paquet keyright, vérifiez les licences hors ligne, activez en ligne, verrouillez les fonctionnalités sur les droits, et gérez les essais et les machines isolées (air-gapped) — en échouant en mode fermé par conception.

Voici le parcours du client Python — de pip install à une application livrée qui débloque des fonctionnalités payantes contre une vraie clé de licence. Le paquet keyright vérifie exactement les mêmes formats de licence et de lease que le SDK .NET, de sorte qu’une gamme de produits multi-langages peut partager un seul tenant Keyright et une seule clé publique. Cette page consacre sa profondeur au code client Python ; les blocs de code utilisent la vraie surface du SDK, copiez-les donc tels quels.

Avant de commencer : la configuration éditeur

Les étapes éditeur, à faire une seule fois, sont indépendantes du langage et sont couvertes en détail ailleurs — faites-les une fois, puis revenez ici pour le client :

  • Créez votre produit et notez son slug (l’identifiant que votre application envoie en tant que product).
  • Copiez votre clé publique de signature — une chaîne SubjectPublicKeyInfo en base64. Ce n’est pas un secret ; elle est livrée dans votre application et ne peut que vérifier des signatures, jamais en produire. Toute la sécurité vient du fait que la clé privée reste chiffrée sur le serveur.
  • Définissez les niveaux et les droits — chaque niveau porte un nombre de sièges et un modèle d’indicateurs nommés et de limites numériques qui sont intégrés dans chaque licence.

Le pas à pas, avec les écrans du tableau de bord et les appels d’API équivalents, se trouve dans Premiers pas et le parcours .NET. Tout ce qui suit suppose que vous avez en main le slug de votre produit et votre clé publique en base64.

Comment ça fonctionne

Vous émettez des clés de licence côté serveur dans Keyright ; chacune est signée avec la clé privée RSA de votre tenant, qui ne quitte jamais le serveur. Votre application Python n’embarque que la clé publique correspondante et vérifie les licences hors ligne avec celle-ci ; une vérification de licence ordinaire n’a donc besoin d’aucun réseau. Pour l’application des sièges et la révocation, vous activez aussi en ligne : l’application échange une clé de licence contre une lease signée de courte durée, liée à la machine, puis continue de fonctionner hors ligne jusqu’à l’expiration de cette lease. Tout ce qui ne passe pas la vérification — mauvaise signature, mauvais produit, expiration, révocation, horloge reculée — se résout en édition Free plutôt que de lever une exception. Le SDK échoue en mode fermé (fail closed).

Installez le SDK

Le paquet est sur PyPI. Il nécessite Python 3.8+ et installe cryptography pour la vérification RSA :

pip install "keyright>=1.1.4"

Tout ce dont vous avez besoin est exporté depuis le paquet de premier niveau :

from keyright import (
    KeyrightClient,
    KeyrightOptions,
    LicenseInfo,
    LicenseStatus,
    Edition,
    MachineFingerprint,
)

Initialisez le client

Construisez un seul KeyrightClient au démarrage avec la fabrique statique initialize. Elle valide les options et charge la clé publique dès le départ (en levant ValueError si la clé manque) ; construisez-le donc une fois et réutilisez-le :

from keyright import KeyrightClient, KeyrightOptions

license = KeyrightClient.initialize(KeyrightOptions(
    product="acme-app",                              # doit correspondre au slug du produit pour lequel vous émettez des clés
    public_key_base64="MIIBIjANBgkq...",             # la clé publique en base64 de votre tableau de bord
    service_url="https://keyright.delta1labs.com",   # omettez-le si vous ne livrez que des fichiers de licence hors ligne
))

Seuls product et public_key_base64 sont réellement à configurer ; service_url n’est requis que pour l’activation en ligne. KeyrightOptions accepte plusieurs arguments nommés facultatifs qui contrôlent d’où une licence est lue et comment elle est validée :

KeyrightOptions(
    product="acme-app",
    public_key_base64="MIIBIjANBgkq...",
    service_url="https://keyright.delta1labs.com",

    # Sources de licence explicites (priorité la plus haute en premier)
    license_string=None,                 # une chaîne JSON de licence passée directement
    license_file_path=None,              # un chemin vers un fichier de licence
    env_var_name="KEYRIGHT_LICENSE",     # variable d'environnement nommant le chemin d'un fichier de licence (c'est la valeur par défaut)

    # Garder une ancienne clé valide pendant une fenêtre de rotation
    additional_public_keys_base64=["<previous public key>"],

    # Révocation hors ligne — livrez une liste de révocation signée avec votre build
    revocation_list_json=None,
    revocation_list_path=None,

    # Tolérance de liaison au nœud : combien de changements de composants « souples » autoriser (1 par défaut)
    node_lock_tolerance=1,
))

Le SDK résout une licence à partir de plusieurs sources, par ordre de priorité (la plus haute en premier) : une license_string explicite, puis un license_file_path explicite, puis le fichier nommé par la variable d’environnement KEYRIGHT_LICENSE, puis config_license_path, puis le chemin app-data du système ({LocalAppData}/Keyright/{product}/license.json), et enfin la lease d’activation en cache. En général, vous ne définissez aucune de ces options — l’activation en ligne écrit le cache de lease pour vous.

Verrouillez les fonctionnalités hors ligne

Appelez validate(). Il résout la meilleure licence parmi les sources ci-dessus, vérifie la signature RSA, la correspondance du produit, la liaison au nœud, l’expiration, toute liste de révocation livrée et l’état d’essai/de manipulation de l’horloge — le tout hors ligne — et il ne lève jamais d’exception. En cas d’échec, il se résout en une LicenseInfo en édition Free portant la raison.

validate() renvoie un tuple (LicenseInfo, source) — dépaquetez-le toujours. source est une courte chaîne de diagnostic ("lease", "environment", "default", "none", …) qui vous indique quelle source l’a emporté :

from keyright import Edition

info, source = license.validate()   # -> (LicenseInfo, str) — dépaquetez le tuple

if info.is_paid:                     # True pour toute édition au-dessus de Free
    enable_paid_features()

if info.edition == Edition.ENTERPRISE:
    enable_enterprise_features()

Si vous ne voulez que la LicenseInfo et que la source vous est indifférente, validate_info() renvoie uniquement le premier élément :

info = license.validate_info()       # équivalent à license.validate()[0]

Verrouillez sur les droits

Préférez verrouiller les fonctionnalités individuelles sur les droits plutôt que sur l’édition, pour que modifier le modèle d’un niveau n’oblige pas à livrer du nouveau code. LicenseInfo.entitlements est un EntitlementSet avec deux accesseurs typés qui échouent en mode fermé :

info = license.validate_info()

# Indicateur booléen — un indicateur absent ou non « vrai » se lit comme False
if info.entitlements.is_enabled("export"):
    show_export_command()

# Limite numérique — passez vous-même la valeur de repli en mode fermé ; une valeur absente ou
# non analysable renvoie votre valeur de repli, et "unlimited" renvoie un très grand entier
max_projects = info.entitlements.get_limit("max-projects", fallback=1)
if current_project_count >= max_projects:
    prompt_to_upgrade()

Pour une vérification rapide isolée, le client expose aussi des raccourcis pratiques qui valident sur le moment — license.is_enabled("export"), license.get_limit("max-projects", 1) et license.edition(). Chacun relance validate() en interne ; quand vous vérifiez plusieurs droits à la fois, validez donc une seule fois et réutilisez l’EntitlementSet :

info = license.validate_info()
ents = info.entitlements
can_export = ents.is_enabled("export")
max_seats = ents.get_limit("max-seats", 1)

is_enabled et get_limit échouent tous deux en mode fermé : un indicateur absent est désactivé, une limite absente ou non analysable renvoie votre valeur de repli. Combinez une vérification d’édition avec une vérification de droit quand une fonctionnalité relève d’un niveau et d’un indicateur de modèle, et que les deux doivent passer.

Lisez le statut pour les diagnostics

Pour savoir pourquoi une vérification s’est résolue comme elle l’a fait — pour une boîte de dialogue « Enregistrer » ou la journalisation — lisez les champs en snake_case de LicenseInfo :

info, source = license.validate()

print(info.status)          # énumération LicenseStatus : VALID, NO_LICENSE, SIGNATURE_INVALID,
                            #   EXPIRED, MACHINE_MISMATCH, REVOKED, CLOCK_TAMPERED, MALFORMED
print(info.message)         # chaîne d'explication destinée aux humains
print(info.licensee)        # à qui la licence a été émise
print(info.edition)         # Edition.FREE | LICENSED | ENTERPRISE
print(info.is_valid)        # True uniquement quand status == LicenseStatus.VALID
print(info.is_paid)         # True pour toute édition au-dessus de Free
print(info.status_badge)    # p. ex. "Enterprise", "Licensed Trial", "Free"
print(info.kind)            # LicenseKind.PERPETUAL | SUBSCRIPTION | TRIAL
print(info.is_trial)        # True pour une licence/lease d'essai
print(info.expiry_utc)      # datetime | None (None = perpétuelle)
print(info.days_remaining)  # int | None — p. ex. 27 pour un badge « 27 jours restants »
print(info.trial_days)      # int | None — la durée configurée de l'essai
print(source)               # quelle source l'a résolue : "lease", "environment", ...

is_valid est la vérification stricte (le statut est VALID) ; is_paid est la vérification « débloquer les fonctionnalités payantes » (édition au-dessus de Free). Un essai valide est les deux à la fois.

Activez en ligne avec une clé de licence

Quand un client saisit une clé, appelez activate(key). Il est synchrone. Il envoie la clé plus un identifiant de machine stable à votre service, qui consomme un siège et renvoie une lease signée de courte durée liée à cette machine. Le SDK vérifie la lease avec votre clé publique embarquée et la met en cache localement, de sorte que chaque validate() ultérieur réussit sans réseau jusqu’à l’expiration de la fenêtre de grâce de la lease.

activate ne lève pas d’exception pour les chemins d’échec ordinaires (mauvaise clé, limite de sièges, hors ligne, révoquée) — il renvoie une LicenseInfo en mode fermé, exactement comme validate(). Il renvoie la LicenseInfo directement (pas un tuple). Inspectez le résultat :

info = license.activate(customer_entered_key)

if info.is_valid and info.is_paid:
    # Activée. La lease est en cache ; l'application fonctionne désormais hors ligne jusqu'à son expiration.
    show_licensed_ui(info.status_badge)     # p. ex. "Enterprise" ou "Enterprise Trial"
else:
    # Affichez info.message ; l'application reste en mode Free.
    show_activation_error(info.message)      # "All seats for this license are in use.", etc.
  • Les sièges sont appliqués côté serveur. Activer plus de machines que la licence ne l’autorise renvoie un résultat de limite de sièges (info.status == LicenseStatus.NO_LICENSE, message « All seats for this license are in use. ») et aucune lease. Réactiver une machine déjà liée est idempotent — aucun siège supplémentaire n’est consommé.
  • Grâce hors ligne. Si le service est injoignable, activate se replie sur toute lease en cache encore valide, de sorte qu’une brève panne ne bloque pas l’utilisateur. Quand la lease approche de l’expiration, l’application doit de nouveau joindre le serveur pour la rafraîchir.
  • La révocation prend effet au prochain rafraîchissement — voir ci-dessous.

activate ne lève ValueError que pour des erreurs de programmation : un service_url manquant ou une clé vide.

Désactivez pour libérer un siège

Pour libérer le siège de cette machine afin qu’elle puisse être réactivée ailleurs, appelez deactivate(key). Il lie le même identifiant de machine que celui utilisé par activate et l’envoie au service. Contrairement à activate, il n’a aucun repli hors ligne — si le service ne peut pas être joint, l’erreur se propage, encadrez donc l’appel :

try:
    status = license.deactivate(license_key)   # -> "ok" | "not_found"
    if status == "ok":
        # Le siège est libéré et la lease en cache local a été supprimée.
        show_free_ui()
except (RuntimeError, OSError) as ex:
    show_error(f"Could not reach the licensing service: {ex}")

Sur "ok", le SDK supprime la lease en cache local pour ce produit, de sorte qu’un validate() suivant retombe en Free.

Machines isolées (air-gapped) : importez une lease hors ligne

Pour une machine qui ne pourra jamais joindre le service, un opérateur signe une lease hors ligne pour son identifiant de machine (Licenses → offline lease dans le tableau de bord, ou POST /admin/licenses/{id}/offline-lease) et livre le JSON par fichier. Pour transmettre l’identifiant de cette machine à l’opérateur, lisez-le localement :

from keyright import MachineFingerprint

machine_id = MachineFingerprint.current().to_bound_string()   # "KRM1:..." — remettez ceci à l'opérateur
print(machine_id)

Importez ensuite la lease livrée. import_offline_lease vérifie la signature, le produit, la machine et la non-expiration par le même chemin qu’une lease en cache, la met en cache localement en cas de succès, et lève ValueError si la lease est invalide — gérez-la donc :

try:
    with open("acme.lease.json", encoding="utf-8") as f:
        info = license.import_offline_lease(f.read())
    # En cache. Les appels validate() suivants réussissent désormais hors ligne.
    show_licensed_ui(info.status_badge)
except ValueError as ex:
    # mauvaise signature / produit / machine, ou expirée
    show_error(str(ex))

Liaison au nœud

Le SDK dérive une empreinte de machine stable à partir d’un composant d’ancrage (le GUID de la machine) plus des signaux « souples » (nom d’hôte, OS). node_lock_tolerance (par défaut 1) laisse passer un petit changement matériel ou d’OS sans bloquer l’utilisateur, tandis que l’ancrage doit toujours correspondre. La requête d’activation envoie cet identifiant de machine pour que les sièges soient comptés par appareil. MachineFingerprint.current().to_bound_string() donne la chaîne exacte à laquelle le serveur lie une lease ; MachineFingerprint.current().display_id donne une forme courte et lisible KR-XXXX-XXXX pour les échanges avec le support.

Essais

Il n’y a pas de méthode d’essai dans le SDK — une clé d’essai s’obtient hors bande en appelant l’endpoint public POST /v1/trial (qui envoie par e-mail la clé au client), puis se passe à activate() exactement comme une clé payante. Une clé d’essai s’active par exactement le même chemin activate qu’une clé payante. Affichez l’état directement depuis LicenseInfo :

info = license.validate_info()
if info.is_trial:
    badge = info.status_badge          # p. ex. "Enterprise Trial"
    left = info.days_remaining         # p. ex. 27  -> « 27 jours restants »
    show_trial_banner(badge, left)

La durée d’un essai est comptée à partir de la première activation sur cette machine (le SDK l’enregistre localement). Quand il expire, validate() renvoie Free avec status == LicenseStatus.EXPIRED et un message indiquant la durée de l’essai. Un essai est remplacé en douceur quand le client active plus tard une clé payante. Flux complet : Essais gratuits en libre-service.

Dépannage

  • L’activation dit que la clé n’a pas été reconnue. La cause la plus fréquente est une non-correspondance de produit : la clé a été émise pour un produit (ou un tenant) différent du slug product avec lequel votre client est configuré. Le serveur limite l’activation au produit, de sorte qu’une clé valide pour acme-app n’activera pas un client initialisé avec product="other-app". Confirmez que le slug correspond au produit sous lequel vous avez émis la clé.
  • Tout se lit comme Free. C’est voulu — le SDK échoue en mode fermé sur tout problème de vérification plutôt que de lever une exception. Lisez info.status (NO_LICENSE, SIGNATURE_INVALID, EXPIRED, MACHINE_MISMATCH, REVOKED, CLOCK_TAMPERED, MALFORMED) et info.message pour savoir lequel. Un SIGNATURE_INVALID signifie presque toujours que le public_key_base64 embarqué ne correspond pas au tenant qui a signé la clé.
  • validate() « renvoie deux choses ». Il renvoie un tuple (LicenseInfo, source) — dépaquetez-le (info, source = license.validate()), ou appelez validate_info() pour n’obtenir que la LicenseInfo.
  • Une licence à durée limitée refuse soudain de se valider. Si l’horloge système est reculée en arrière au-delà de la tolérance (24 h par défaut) sur un essai ou un abonnement, le SDK considère cela comme une manipulation de l’horloge et renvoie le statut CLOCK_TAMPERED. Ce n’est pas définitif — remettez l’heure correcte et la validation se rétablit à l’appel suivant. Les licences perpétuelles ne sont jamais soumises à cette vérification.
  • ValueError depuis activate ou deactivate. Ils ne lèvent une exception que pour des erreurs de programmation — un service_url manquant ou une clé vide. Les échecs de licence ordinaires reviennent sous forme de LicenseInfo, pas d’exception.

Voir aussi