Skip to content

Tenant APIs

Tenant scopes store an immutable identity in the current Python context. The context helpers perform no database I/O. Configured adapter strategies apply the captured payload to a connection lease; database policies enforce row access.

Session Providers

Pass an authorized tenant directly to a tenant-enabled config:

async with config.provide_session(tenant="tenant-a") as session:
    rows = await session.select("SELECT id, name FROM projects")

async with config.provide_session(bypass_tenant=True) as lookup:
    user = await lookup.select_one_or_none(
        "SELECT id FROM users WHERE email = :email", email="[email protected]"
    )

Sync configs accept the same keywords with with, and SQLSpec.provide_session(config, ..., tenant=..., bypass_tenant=...) forwards both arguments. tenant accepts a string, UUID, or TenantContextPayload (use a payload to supply extra session variables); bypass_tenant=True binds TenantContextPayload.bypass() for a single pre-tenant lookup or health check and cannot be combined with tenant. The tenant or bypass state stays bound through session cleanup and restores the prior context on every exit. tenant=None with bypass_tenant=False inherits the ambient scope. Both keywords require a configured tenant_strategy; unsupported adapters reject them.

All tenant-enabled adapters accept tenant_strategy=True and tenant_strategy="default". Adapter-specific shorthands select the same built-in strategy. Other adapters, including ADBC and Spanner, reject tenant_strategy:

Database Adapters Shorthand Built-in behavior
PostgreSQL asyncpg, psycopg, psqlpy "rls" Sets session variables for database row policies.
CockroachDB cockroach_asyncpg, cockroach_psycopg "rls" Sets session variables for database row policies.
SQL Server mssql_python, pymssql "rls" or "session_context" Sets mutable SESSION_CONTEXT keys for database row policies.
Oracle oracledb "identifier" Sets client-identifier metadata.
IBM Db2 db2 "rcac" or "global_variable" Sets session global variables for row and column access control.
Snowflake snowflake "rls" or "session_attributes" Sets immutable session attributes for row access policies on one connection per lease.

Oracle VPD requires an explicit OracleSyncVPDStrategy or OracleAsyncVPDStrategy with trusted setter, clearer, namespace, and attribute names. There is no parameter-free "vpd" shorthand. See the Oracle reference.

from sqlspec import TenantContext, TenantContextPayload

tenant = TenantContextPayload("tenant-a", {"region": "west"})
with TenantContext.scope(tenant):
    print(TenantContext.get_tenant_id())
    with TenantContext.bypass():
        print(TenantContext.is_bypassed())
        print(TenantContext.get_tenant_id())
    print(TenantContext.get_tenant_id())

This prints tenant-a, True, None, and tenant-a in order.

TenantContextPayload accepts a nonblank string or UUID, which it converts to a string. Identities must be NUL-free and at most 256 UTF-8 bytes (MAX_TENANT_ID_BYTES). Variables are copied into a read-only mapping; keys and values must be NUL-free strings. An existing payload passed to scope() is reused unless variables supplies merged overrides.

get() returns the payload or None when unset. get_tenant_id() returns None for both unset and bypassed contexts; use is_bypassed() to distinguish explicit bypass. A bypass payload contains no identity or variables.

Synchronous and asynchronous scopes restore the preceding payload on normal exit, exceptions, and cancellation. Both TenantContext.scope(...) and TenantContext.bypass() implement the synchronous and asynchronous context-manager protocols directly:

from sqlspec import TenantContext


async def read_tenant_context() -> tuple[str | None, bool]:
    async with TenantContext.scope("tenant-a"):
        tenant_id = TenantContext.get_tenant_id()
        async with TenantContext.bypass():
            bypassed = TenantContext.is_bypassed()
        return tenant_id, bypassed

Async tasks inherit context when created, and changes stay local to their context. asyncio.to_thread() copies context; unmanaged threads and direct executor submissions require an explicit contextvars.copy_context().

Prefer scopes for automatic restoration. For manual binding, set(payload) or set(None) returns a token for reset(token) in the original context. tenant_context_var is available from sqlspec.utils.tenant for direct context access.

Invalid identities, variable entries, bypass payloads, or values passed to set() raise TenantContextError, a subclass of both SQLSpecError and ValueError. Token misuse follows ContextVar.reset() errors. TenantCleanupError reports failed connection cleanup; context scopes do not raise it. See the exception reference for both error types.

All TenantContext methods are class methods:

Method Return and behavior
get() Current TenantContextPayload, or None when unset; inside a framework request it resolves the request tenant on first use.
aget() Awaitable form of get() that also awaits an asynchronous request extractor.
require(key=None) Current payload, or the framework's 401/403 inside a request and TenantContextError outside one; key selects a database configuration.
arequire(key=None) Awaitable form of require().
get_tenant_id() Current identity string, or None when unset or bypassed.
is_bypassed() Whether explicit bypass state is active.
set(payload) Restoration token; accepts TenantContextPayload or None.
reset(token) Restores the preceding state; returns None.
scope(tenant_id, *, variables=None) Context manager for with or async with yielding a payload; accepts a string, UUID, or existing payload.
bypass() Context manager for with or async with yielding TenantContextPayload.bypass().
bind(payload) Sync context manager binding an already resolved payload, or None, around connection work.
bind_request(binding) Binds a TenantRequestBinding and returns its restoration token; framework plugins call it at request start.
current_binding() Raw bound value (payload, request binding, or None) without resolving anything.

TenantContextPayload

Snapshot of a tenant identity and its session variables.

bypass
bypass()

Create an explicit bypass without tenant identity or variables.

TenantContext

Manage tenant state in the current task or explicitly copied context.

TenantContextError

Bases: SQLSpecError, ValueError

Raised when tenant identity is missing, invalid, or mismatched.

TenantCleanupError

Bases: SQLSpecError

Raised when tenant cleanup fails and the connection is invalidated.

Request Extraction

Extractors return an app-authorized TenantContextPayload, string, UUID, or None. Inside a framework plugin, None means the extractor has no tenant and resolution continues with the ambient scope; see One Request Contract. SQLSpecTenantMiddleware binds its extractor result, including None, for the whole ASGI call. Middleware restores the caller's token on HTTP, WebSocket, errors, and cancellation. Bypass skips strategy checkout/checkin; it does not disable database policies.

coerce_tenant_payload

coerce_tenant_payload(value)

Validate an application-authorized tenant extractor result.

Parameters:

Name Type Description Default
value object

Verified tenant identity, immutable payload, explicit bypass, or None.

required

Returns:

Type Description
TenantContextPayload | None

An immutable payload, preserving None and existing payloads.

Raises:

Type Description
TenantContextError

The result is an unsupported type or invalid identity.

SQLSpecTenantMiddleware

Bind an application-authorized tenant for an entire ASGI invocation.

__init__

__init__(app, *, extractor)

Configure the ASGI application and trusted tenant extractor.

__call__

__call__(scope, receive, send)

Bind HTTP and WebSocket contexts and restore the caller on every exit.

single_membership_tenant_extractor

single_membership_tenant_extractor(memberships)

Select a tenant only when the application reports exactly one membership.

Parameters:

Name Type Description Default
memberships Callable[[MutableMapping[str, Any]], Collection[str]]

Trusted accessor for memberships verified by the application.

required

Returns:

Type Description
TenantExtractor

An extractor that refuses to choose between multiple memberships.

TenantRequestBinding

Resolve a framework request's tenant lazily, once per database configuration.

A plugin binds one instance at request start. Nothing runs until a lease, a session, or TenantContext asks for the tenant; the request-specific resolver then runs once per configuration key and its result is memoized. A resolver that yields no tenant falls back to the ambient value captured when the request started. After freeze() the binding only answers from memoized results, so work that outlives the response never reads request state.

frozen

frozen

Return whether request state may no longer be read.

__init__

__init__(resolver, *, keys, ambient=None, missing=None)

Configure request resolution.

Parameters:

Name Type Description Default
resolver Callable[[Any], object]

Callable returning the route tenant for a configuration key as a payload, string, UUID, or None, or an awaitable of one.

required
keys Sequence[Any]

Tenant-enabled configuration keys; the first is the default key.

required
ambient TenantContextPayload | TenantRequestBinding | None

Tenant state bound outside the request, used when route resolution yields none.

None
missing Callable[[Any], None] | None

Callable raising the framework's unauthenticated or forbidden response for a key.

None

resolve

resolve(key=None)

Return the tenant for key, resolving it synchronously on first use.

Raises:

Type Description
ImproperConfigurationError

The request resolves its tenant asynchronously.

aresolve

aresolve(key=None)

Return the tenant for key, awaiting an asynchronous resolver on first use.

require

require(key=None)

Return the tenant for key or reject the request.

Raises:

Type Description
TenantContextError

No tenant resolved and no framework response applies.

arequire

arequire(key=None)

Return the tenant for key or reject the request, awaiting asynchronous resolution.

Raises:

Type Description
TenantContextError

No tenant resolved and no framework response applies.

freeze

freeze()

Resolve every pending key from request state, then stop reading it.

Called when work is handed off past the response. Resolution failures leave the key without a tenant instead of failing the response.

afreeze

afreeze()

Resolve every pending key from request state asynchronously, then stop reading it.

tenant_bypass

tenant_bypass(handler)

Mark a route handler as an explicit tenant bypass.

Framework plugins resolve a bypass payload for marked routes, so public routes such as login or health checks can use tenant-enabled sessions without a tenant.

Parameters:

Name Type Description Default
handler Any

Route function or handler object.

required

Returns:

Type Description
Any

The same handler.

Strategy and Lease Contracts

A strategy returns TenantLeaseState or a baseline Mapping[str, str | None] from checkout. The lease freezes a returned mapping with the captured payload before checkin. Implement these interfaces structurally. Interpreted strategies must not inherit compiled protocols. Built-in adapters own connection acquisition, transaction completion, release, and physical disposal through SyncTenantLease and AsyncTenantLease subclasses. A config with a tenant strategy but no lease implementation refuses to hand out connections.

TenantLeaseState

Capture the payload and a read-only baseline for one acquisition.

__init__

__init__(payload, baseline=None)

SyncTenantStrategy

Bases: Protocol

Apply and restore tenant state on a synchronous connection.

checkout

checkout(connection, payload)

checkin

checkin(connection, state)

AsyncTenantStrategy

Bases: Protocol

Apply and restore tenant state on an asynchronous connection.

checkout

checkout(connection, payload)

checkin

checkin(connection, state)

Litestar Tenant Resolution

Configure extension_config["litestar"] on SQLSpecPlugin(config) or attach require_tenant(), which inherits the registered plugin's TenantResolutionSettings automatically unless overridden per route. resolve_litestar_tenant also integrates litestar-security Principal, SecurityContext, AuthorizationSnapshot (tenant_ids and tenant_roles), CredentialRestrictions, SessionHandle, requires_tenant, and requires_tenant_role. set_connection_tenant(..., key=...) scopes a receipt to one database configuration. Other frameworks expose set_request_tenant(...) from sqlspec.extensions.{fastapi,starlette,flask,sanic} with the same key= argument. See Litestar Multi-Tenancy and the other framework guides for complete examples.

TenantResolutionSettings

Names used to resolve verified identities and authorize tenant selectors.

require_tenant

require_tenant(
    *,
    path_parameter=_UNSET,
    header=_UNSET,
    session_key=_UNSET,
    user_attr=_UNSET,
    memberships_attr=_UNSET,
    variables_attr=_UNSET,
    jwt_claim=_UNSET,
    config_key=None,
)

Require authentication and an authorized tenant before route execution.

Defaults inherit from the active SQLSpecPlugin configuration on app.state.sqlspec, falling back to TenantResolutionSettings() when no plugin is registered. Explicit keyword arguments override inherited fields.

Parameters:

Name Type Description Default
path_parameter str | None

Optional route parameter containing a tenant selector.

_UNSET
header str | None

Optional request header containing a tenant selector.

_UNSET
session_key str | None

Session key containing a tenant selector.

_UNSET
user_attr str

Verified direct tenant attribute or mapping key.

_UNSET
memberships_attr str

Verified membership attribute or mapping key.

_UNSET
variables_attr str | None

Extra tenant session variables attribute or mapping key.

_UNSET
jwt_claim str

Verified token claim containing a direct tenant identity.

_UNSET
config_key Any

Optional database config key, type, or instance used to select base settings when multiple tenant databases are registered.

None

Returns:

Type Description
Guard

An asynchronous Litestar authorization guard.

is_scope_authenticated

is_scope_authenticated(scope)

Return whether an ASGI scope carries verified caller authentication.

set_connection_tenant

set_connection_tenant(
    connection_or_scope, tenant_id, *, variables=None, key=None
)

Store a tenant receipt after trusted application code authorizes access.

Parameters:

Name Type Description Default
connection_or_scope Any

A Litestar connection or its raw ASGI scope.

required
tenant_id str | UUID | TenantContextPayload

The authorized tenant identity or an existing payload.

required
variables Mapping[str, str] | None

Extra session variables merged with an existing payload.

None
key str | None

Session key, connection key, or bind key of one database configuration; None stores a receipt shared by every configuration without a keyed receipt.

None

Returns:

Type Description
TenantContextPayload

The immutable payload stored on the connection scope.

resolve_litestar_tenant

resolve_litestar_tenant(scope, settings, *, receipt_keys=())

Resolve an authorized tenant without changing the ambient context.

Route bypass and explicit trusted receipts precede an application extractor. Automatic selection checks path, session and optional header values against authenticated memberships; route guard declarations only name selectors.

Parameters:

Name Type Description Default
scope MutableMapping[str, Any]

The ASGI connection scope.

required
settings TenantResolutionSettings

Tenant resolution settings for the database configuration.

required
receipt_keys Sequence[str]

Configuration keys whose keyed receipts precede the shared receipt.

()

Returns:

Type Description
Awaitable[TenantContextPayload | None] | TenantContextPayload | None

The resolved payload, None, or an awaitable resolving to either.

See Multi-Tenancy for policy setup and adapter references for concrete strategies.