Context¶
TenantContext¶
TenantContext ¶
Namespace for async-safe per-request tenant context.
All methods are static. This class is never instantiated — it exists purely as a namespace grouping related operations.
Usage in ASGI middleware::
token = TenantContext.set(tenant)
try:
await call_next(request)
finally:
TenantContext.reset(token) # restore, not clear
Usage in route handlers / dependencies::
tenant = TenantContext.get() # raises if not set
tenant = TenantContext.get_optional() # returns None if not set
set
staticmethod
¶
Set tenant as the current request's tenant.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tenant
|
Tenant
|
The resolved tenant to make current. |
required |
Returns:
| Type | Description |
|---|---|
Token[Tenant | None]
|
A |
Token[Tenant | None]
|
Always pass this token to |
Token[Tenant | None]
|
|
Source code in src/fastapi_tenancy/core/context.py
get
staticmethod
¶
get() -> Tenant
Return the current tenant; raise TenantNotFoundError if unset.
Returns:
| Type | Description |
|---|---|
Tenant
|
The currently active |
Raises:
| Type | Description |
|---|---|
TenantNotFoundError
|
When called outside a tenancy-aware request (e.g. from a background task that did not set the context). |
Source code in src/fastapi_tenancy/core/context.py
get_optional
staticmethod
¶
get_optional() -> Tenant | None
Return the current tenant, or None if none is set.
Use this variant for endpoints that can serve both anonymous and tenant-scoped requests.
Returns:
| Type | Description |
|---|---|
Tenant | None
|
The active |
Source code in src/fastapi_tenancy/core/context.py
| Python | |
|---|---|
reset
staticmethod
¶
reset(token: Token[Tenant | None]) -> None
Restore the tenant context to the state captured in token.
Prefer this over clear() when managing nested scopes — it
correctly restores a previous tenant rather than unconditionally
setting the context to None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
Token[Tenant | None]
|
Token returned by a previous |
required |
Source code in src/fastapi_tenancy/core/context.py
clear
staticmethod
¶
clear() -> tuple[
Token[Tenant | None], Token[dict[str, Any] | None]
]
Set both the tenant and all metadata to None in the current context.
Returns tokens that callers can pass to :meth:reset_all to restore
the previous state. This makes clear() safe for use at any scope
depth — not just the outermost one.
When to use clear() vs reset(token)
-
:meth:
reset/reset_all: prefer this in all middleware and nested-scope code where a previous state (possibly a non-Nonetenant) should be restored on exit. This is what :func:tenant_scopeand :class:~fastapi_tenancy.middleware.tenancy.TenancyMiddlewareuse. -
clear()+ :meth:reset_all: use this in test fixtures and application startup/shutdown where you want to unconditionally discard any inherited context. Callreset_all(*clear())in the fixture teardown to restore whatever was there before (usuallyNone, but safe either way).
Returns:
| Type | Description |
|---|---|
Token[Tenant | None]
|
A |
Token[dict[str, Any] | None]
|
meth: |
Example — test fixture (safe at any nesting depth):
.. code-block:: python
@pytest.fixture(autouse=True)
def clean_context():
tokens = TenantContext.clear()
yield
TenantContext.reset_all(*tokens)
Source code in src/fastapi_tenancy/core/context.py
reset_all
staticmethod
¶
reset_all(
tenant_token: Token[Tenant | None],
metadata_token: Token[dict[str, Any] | None],
) -> None
Restore both context variables to the state captured by clear().
This is the counterpart to :meth:clear. Use it in finally
blocks or fixture teardowns to restore exactly the state that existed
before clear() was called, regardless of scope depth.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tenant_token
|
Token[Tenant | None]
|
Token returned by a previous :meth: |
required |
metadata_token
|
Token[dict[str, Any] | None]
|
Token returned by the same :meth: |
required |
Example:
.. code-block:: python
tokens = TenantContext.clear()
try:
...
finally:
TenantContext.reset_all(*tokens)
Source code in src/fastapi_tenancy/core/context.py
set_metadata
staticmethod
¶
Attach a key-value pair to the current request's tenant context.
Metadata is isolated per request, just like the tenant itself. It is useful for propagating request-scoped state (request ID, user ID, feature flags) without threading it through every function signature.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Metadata key. |
required |
value
|
Any
|
Metadata value (any JSON-serialisable type recommended). |
required |
Example::
TenantContext.set_metadata("request_id", str(uuid4()))
TenantContext.set_metadata("user_id", "user-abc")
Source code in src/fastapi_tenancy/core/context.py
get_metadata
staticmethod
¶
Retrieve a metadata value from the current request context.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Metadata key. |
required |
default
|
Any
|
Value returned when the key is absent. |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The stored value, or default when the key does not exist. |
Source code in src/fastapi_tenancy/core/context.py
get_all_metadata
staticmethod
¶
Return a copy of all metadata in the current context.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A plain dictionary. Mutating it does not affect the context. |
Source code in src/fastapi_tenancy/core/context.py
| Python | |
|---|---|
clear_metadata
staticmethod
¶
Set all metadata to None while keeping the tenant set.
Returns a token that callers can pass to _metadata_ctx.reset(token)
to restore the previous metadata state. This makes the method safe for
use in nested scopes — not just at the outermost request level.
Useful in middleware that wants to reset per-request supplementary data without disturbing the tenant identity.
Returns:
| Type | Description |
|---|---|
Token[dict[str, Any] | None]
|
A |
Example:
.. code-block:: python
token = TenantContext.clear_metadata()
try:
...
finally:
_metadata_ctx.reset(token) # or just let it drop at outermost scope
Source code in src/fastapi_tenancy/core/context.py
Context manager¶
tenant_scope
async
¶
Async context manager that activates a tenant scope for a block.
Sets tenant as the current tenant for the duration of the async with
block and restores the previous state on exit — even if an exception is
raised. This is the recommended pattern for background tasks and tests.
Unlike middleware-style usage, this always uses reset(token) to
restore, not clear(), making it safe for nested usage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tenant
|
Tenant
|
The tenant to activate for the duration of the block. |
required |
Yields:
| Type | Description |
|---|---|
AsyncIterator[Tenant]
|
The active |
Example — background task::
async with tenant_scope(tenant) as t:
await process_tenant_data(t)
# Previous context (usually None) is restored here.
Example — nested scopes::
async with tenant_scope(outer_tenant):
async with tenant_scope(inner_tenant):
assert TenantContext.get() is inner_tenant
assert TenantContext.get() is outer_tenant
Source code in src/fastapi_tenancy/core/context.py
FastAPI dependency functions¶
get_current_tenant ¶
get_current_tenant() -> Tenant
FastAPI dependency — return the current tenant or raise 500.
Inject this via Depends in any route that requires a tenant::
from fastapi import Depends
from fastapi_tenancy.core.context import get_current_tenant
@app.get("/users")
async def list_users(tenant: Tenant = Depends(get_current_tenant)):
...
The middleware populates the context before calling route handlers, so
this dependency succeeds for every request that passes through
TenancyMiddleware.
Returns:
| Type | Description |
|---|---|
Tenant
|
The currently active |
Raises:
| Type | Description |
|---|---|
TenantNotFoundError
|
When no tenant is set (route bypassed the middleware — misconfiguration). |
Source code in src/fastapi_tenancy/core/context.py
get_current_tenant_optional ¶
get_current_tenant_optional() -> Tenant | None
FastAPI dependency — return the current tenant or None.
Use in routes that can serve both anonymous and tenant-scoped requests::
@app.get("/status")
async def status(tenant: Tenant | None = Depends(get_current_tenant_optional)):
if tenant:
return tenant_status(tenant)
return global_status()
Returns:
| Type | Description |
|---|---|
Tenant | None
|
The active |