Source code for ferrosoft.apps.ferrobase.tenant.context

#  Copyright (c) 2026 Ferrosoft GmbH. All rights reserved.
"""Context-variable storage for the currently active tenant.

Multi-tenancy is propagated through a single ``ContextVar`` so that both
threaded and asynchronous request handling pick up the correct tenant without
any explicit argument passing.  ORM helpers in
``ferrosoft.apps.ferrobase.tenant.utils`` consult this variable to pick the
right database alias.
"""

from contextlib import contextmanager
from contextvars import ContextVar
from typing import Iterator

from ferrosoft.apps.ferrobase.models import Tenant

_tenant: ContextVar[Tenant | None] = ContextVar("tenant", default=None)


[docs] def get_current_tenant() -> Tenant | None: """Return the tenant active in the current execution context, or ``None``.""" return _tenant.get()
[docs] def set_current_tenant(tenant: Tenant | None): """Set the active tenant for the current execution context. Performs no database side effects and does not restore the previous value on exit. Prefer :func:`active_tenant` whenever the activation is scoped to a block of code. Args: tenant: The tenant to activate, or ``None`` to clear the context. """ _tenant.set(tenant)
[docs] @contextmanager def active_tenant(tenant: Tenant | None) -> Iterator[None]: """Activate ``tenant`` for the duration of the ``with`` block. On enter the tenant's database connection is activated (when ``tenant`` is not ``None``) and the context variable is set. On exit the previous context value is restored via the token-based reset pattern, so nested activations behave correctly. Args: tenant: Tenant to activate, or ``None`` to enter a no-tenant scope. Yields: Tenant | None: The tenant that was activated. """ if tenant is not None: tenant.activate_connection() token = _tenant.set(tenant) try: yield tenant finally: _tenant.reset(token)
__ALL__ = ["get_current_tenant", "tenant_context"]