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.
TenantContext
¶
Manage tenant state in the current task or explicitly copied context.
TenantContextError
¶
TenantCleanupError
¶
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
¶
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
¶
single_membership_tenant_extractor
¶
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.
__init__
¶
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
¶
Return the tenant for key, resolving it synchronously on first use.
Raises:
| Type | Description |
|---|---|
ImproperConfigurationError
|
The request resolves its tenant asynchronously. |
aresolve
¶
Return the tenant for key, awaiting an asynchronous resolver on first use.
require
¶
Return the tenant for key or reject the request.
Raises:
| Type | Description |
|---|---|
TenantContextError
|
No tenant resolved and no framework response applies. |
arequire
¶
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
¶
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
¶
Resolve every pending key from request state asynchronously, then stop reading it.
tenant_bypass
¶
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.
SyncTenantStrategy
¶
AsyncTenantStrategy
¶
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
¶
Return whether an ASGI scope carries verified caller authentication.
set_connection_tenant
¶
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 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.