SDK

SDK Python

Client synchrone pour Python 3.10 et suivants. Chiffrement local, gestion des clés, journal d’audit.

Ce client est synchrone

Aucune méthode n’est une coroutine. Un await devant un appel MAFATE lève une TypeError. Dans un service asynchrone, enveloppez l’appel dans run_in_executor si le blocage vous gêne, plutôt que d’ajouter un await qui ne peut pas fonctionner.

Installation

# Mode serveur uniquement : requests et ses dependances, rien de natif.
pip install mafate

# ⏩ MODE ENVELOPPE, c'est-a-dire le mode par defaut de tout compte :
pip install mafate[envelope]

# L'extra tire `cryptography`, une extension native. Sans lui, l'import de
# encrypt_local echoue : le defaut se voit au demarrage, pas en production.

Construction du client

import os
from mafate import Mafate

client = Mafate(
    api_key=os.environ["MAFATE_API_KEY"],   # eaas_sk_...
    base_url="https://api.mafate.io",       # defaut
    timeout=30.0,                            # secondes, defaut
)

Chiffrer et déchiffrer

KEY_ID = os.environ["MAFATE_KEY_ID"]  # UUID rendu par POST /v1/keys

# Mode enveloppe : le clair ne quitte jamais ce processus.
enveloppe = client.encrypt_local("donnee sensible", KEY_ID)
# {'ciphertext': ..., 'wrapped_key': ..., 'iv': ...,
#  'key_id': ..., 'key_version': 3, 'envelope_version': 1}

# Stockez le dictionnaire ENTIER. Les six champs sont necessaires.
clair = client.decrypt_local_to_str(enveloppe)

# Charge binaire : la variante courte rend les OCTETS.
octets = client.decrypt_local(enveloppe)

# ⚠️ Mode serveur, refuse par defaut depuis le 11/08/2026 (403 ApiError) :
scelle = client.encrypt("donnee sensible", KEY_ID)
clair = client.decrypt(scelle)

Rendre un champ cherchable

# ⚠️ SEUL APPEL QUI TRANSMET DU CLAIR A MAFATE, MEME EN MODE ENVELOPPE.
# Normalisez avant de hacher, sinon la recherche echoue sur la casse.
empreinte = client.hash(email.lower(), KEY_ID)

Ressources

# Cles de chiffrement. `Key` est un TypedDict : acces par cle, pas par attribut.
cles = client.keys.list()
cle = client.keys.create(name="production-users-pii")
print(cle["id"])                       # l'UUID a conserver

client.keys.rotate(cle["id"])
client.keys.set_rotation_policy(cle["id"], 30)   # None pour desactiver
client.keys.disable(cle["id"])                   # DELETE, irreversible
export = client.keys.export(cle["id"])           # permission keys:export

# Cles d'API
client.api_keys.list()
client.api_keys.create(name="service-inscription", permissions=["keys:wrap"])

# ⏩ mafate.CLEAR efface un champ optionnel, la ou None signifie \"ne pas toucher\".
import mafate
client.api_keys.update("apk_1", expires_at=mafate.CLEAR)
client.api_keys.revoke("apk_1")

# Journal d'audit : un dictionnaire de filtres, un dictionnaire en retour.
page = client.audit.list({"action": "key.unwrap", "limit": 100})
for entree in page["logs"]:
    print(entree["created_at"], entree["actor"], entree["key_id"])

Erreurs

from mafate import ApiError, ConnectionError, TimeoutError, MafateError

try:
    enveloppe = client.encrypt_local(valeur, KEY_ID)
except ApiError as e:
    # Le serveur a repondu : e.status, e.title, e.detail, repris du RFC 7807.
    # ⛔ Il n'existe pas de e.code : un switch dessus ne branche jamais.
    if e.status == 403:
        raise RuntimeError(f"refus definitif : {e.detail}")
    raise
except (ConnectionError, TimeoutError):
    # Le serveur n'a rien repondu : ni statut ni corps. Reessayer a du sens.
    ...
except MafateError:
    # Classe de base. Levee aussi pour une enveloppe malformee, detectee en local.
    ...

Dans un service asynchrone

Le client bloque le temps d’un aller-retour HTTP. Sur une route FastAPI, cela suffit à retenir la boucle d’événements. Si votre trafic le justifie :

import asyncio
from functools import partial

async def sceller(valeur: str, key_id: str) -> dict:
    boucle = asyncio.get_running_loop()
    return await boucle.run_in_executor(
        None, partial(client.encrypt_local, valeur, key_id)
    )

# ⚠️ Le `await` porte ici sur run_in_executor, PAS sur la methode du SDK.
# C'est la difference entre du code qui tourne et une TypeError.