============================ Gestion du Contexte Tenant ============================ Les context variables (``contextvars``) propagent le tenant, l'utilisateur et la membership courants à travers toute la pile d'appels, y compris les coroutines ASGI. Module : ``src.share_kernel.infrastructure.context`` Variables de contexte ===================== .. module:: src.share_kernel.infrastructure.context :synopsis: Contextvars pour la propagation tenant/user/membership .. data:: current_tenant_var :type: ContextVar[Any] Instance du tenant courant (ou ``None`` pour les routes publiques). .. data:: current_user_var :type: ContextVar[Any] Instance de l'utilisateur authentifié courant. .. data:: current_membership_var :type: ContextVar[Any] Instance du ``TenantMembership`` pour l'utilisateur dans le tenant actif. .. data:: correlation_id_var :type: ContextVar[str | None] ID de corrélation pour le traçage distribué (HTTP → Celery). .. data:: cross_tenant_mode_var :type: ContextVar[bool] Flag indiquant que le mode cross-tenant est actif. Les managers vérifient ce flag pour désactiver le filtrage automatique. Accesseurs ========== .. function:: get_current_tenant() -> Any Retourne le tenant courant, ou ``None``. .. function:: get_current_user() -> Any Retourne l'utilisateur courant, ou ``None``. .. function:: get_current_membership() -> Any Retourne la membership courante, ou ``None``. .. function:: get_correlation_id() -> str | None Retourne l'ID de corrélation courant, ou ``None``. Context Manager : tenant_context ================================= .. function:: tenant_context(tenant_or_id, *, user=None, membership=None) Context manager pour basculer vers un tenant spécifique. Accepte un objet ``Tenant``, un ``UUID``, ou un slug (string). Active le backend d'isolation et configure les contextvars pour la durée du bloc, puis restaure l'état précédent. :param tenant_or_id: Tenant, UUID, ou slug :param user: Utilisateur optionnel à associer :param membership: Membership optionnelle à associer :yields: L'instance Tenant résolue .. code-block:: python from src.share_kernel.infrastructure.context import tenant_context # Par objet Tenant with tenant_context(my_tenant) as tenant: products = Product.objects.all() # Scopé au tenant # Par slug with tenant_context("acme-corp"): do_something() # Par UUID with tenant_context(uuid.UUID("...")) as tenant: orders = Order.objects.all() # Avec utilisateur et membership with tenant_context(tenant, user=admin_user, membership=admin_membership): # PIP peut résoudre les attributs de l'utilisateur policy_check() Context Manager : async_tenant_context ======================================= .. function:: async_tenant_context(tenant_or_id, *, user=None, membership=None) Version asynchrone de :func:`tenant_context` pour les vues ASGI. .. code-block:: python async with async_tenant_context("acme-corp") as tenant: products = await Product.objects.all().aall() Context Manager : cross_tenant_context ======================================= .. function:: cross_tenant_context() Désactive temporairement l'isolation tenant pour les opérations cross-tenant (admin, reporting, migrations de données). - Désactive le backend d'isolation - Suspend RLS si applicable - Efface les contextvars tenant/user/membership - Active le flag ``cross_tenant_mode_var`` **Prérequis PostgreSQL (RLS)** : le rôle DB doit avoir ``BYPASSRLS``. .. code-block:: python from src.share_kernel.infrastructure.context import cross_tenant_context from django.db.models import Sum with cross_tenant_context(): # Pas de filtrage tenant — requêtes sur tous les tenants all_orders = Order.objects.all() total = all_orders.aggregate(Sum("amount")) print(f"Revenu total multi-tenant: {total}") .. warning:: Utiliser avec précaution. Le code dans ce bloc a accès à **toutes** les données de **tous** les tenants. Diagramme de flux ================= .. code-block:: text TenantMiddleware │ ├── Résout le tenant (JWT/Header) ├── current_tenant_var.set(tenant) ├── current_user_var.set(user) ├── current_membership_var.set(membership) ├── correlation_id_var.set(uuid4()) ├── backend.activate_tenant(tenant, connection) │ ▼ Traitement de la requête │ ├── TenantSpecificManager.get_queryset() │ └── Lit current_tenant_var pour filtrer │ ├── PIP.resolve_attributes() │ └── Lit current_user_var, current_membership_var │ ├── TenantContextFilter (logging) │ └── Lit tenant_id, user_id, correlation_id │ └── tenant_cache_key() └── Lit current_tenant_var pour préfixer │ ▼ Fin de requête │ ├── backend.deactivate_tenant(connection) └── Reset de tous les contextvars