Domain Types¶
Tenant¶
Tenant ¶
Bases: BaseModel
Immutable tenant domain object.
All instances are frozen (ConfigDict(frozen=True)). To produce a
modified copy use Pydantic's model_copy::
updated = tenant.model_copy(update={"status": TenantStatus.SUSPENDED})
Attributes:
| Name | Type | Description |
|---|---|---|
id |
str
|
Opaque unique identifier (UUID or any stable string). This is the internal primary key and should be opaque to end-users. |
identifier |
str
|
Human-readable slug used in URLs, headers, and subdomains
(e.g. |
name |
str
|
Display name shown in UIs and reports. |
status |
TenantStatus
|
Current |
isolation_strategy |
IsolationStrategy | None
|
Per-tenant override; |
metadata |
dict[str, Any]
|
Arbitrary key-value store for application-specific configuration (plan, quotas, feature flags, …). |
created_at |
datetime
|
Creation timestamp in UTC. |
updated_at |
datetime
|
Last-modification timestamp in UTC. |
database_url |
str | None
|
Connection URL used in |
schema_name |
str | None
|
Schema name override used in |
is_active ¶
is_suspended ¶
is_deleted ¶
is_provisioning ¶
model_dump_safe ¶
Return a serialisable dict with database_url masked.
Use this method when including tenant data in logs, error responses, or audit trails to avoid leaking connection-string credentials.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Plain dictionary with |
dict[str, Any]
|
when the field is set. |
Source code in src/fastapi_tenancy/core/types.py
TenantConfig¶
TenantConfig ¶
Bases: BaseModel
Per-tenant quota and feature configuration.
Built from Tenant.metadata by the get_tenant_config dependency.
All fields have sensible defaults so the dependency never raises even
when a tenant has an empty metadata dict.
Attributes:
| Name | Type | Description |
|---|---|---|
max_users |
int | None
|
Maximum number of users allowed ( |
max_storage_gb |
int | None
|
Maximum storage in gigabytes ( |
features_enabled |
list[str]
|
List of feature-flag strings enabled for this tenant. |
rate_limit_per_minute |
int
|
Per-tenant API rate limit. |
custom_settings |
dict[str, Any]
|
Arbitrary extra configuration for application use. |
AuditLog¶
AuditLog ¶
Bases: BaseModel
Immutable audit-log entry for a tenant operation.
Attributes:
| Name | Type | Description |
|---|---|---|
tenant_id |
str
|
Tenant whose resource was affected. |
user_id |
str | None
|
Authenticated user ( |
action |
str
|
Verb describing the operation (e.g. |
resource |
str
|
Resource type (e.g. |
resource_id |
str | None
|
Identifier of the specific resource ( |
metadata |
dict[str, Any]
|
Supplementary context (diff, old values, …). |
ip_address |
str | None
|
Client IP address. |
user_agent |
str | None
|
Client user-agent string. |
timestamp |
datetime
|
Event timestamp in UTC. |
TenantMetrics¶
TenantMetrics ¶
Bases: BaseModel
Snapshot of a tenant's usage metrics.
Attributes:
| Name | Type | Description |
|---|---|---|
tenant_id |
str
|
Owning tenant ID. |
requests_count |
int
|
Total number of HTTP requests handled. |
storage_bytes |
int
|
Storage consumed in bytes. |
users_count |
int
|
Number of active users. |
api_calls_today |
int
|
API calls in the current calendar day (UTC). |
last_activity |
datetime | None
|
Timestamp of the most recent request ( |
Enumerations¶
TenantStatus¶
TenantStatus ¶
Bases: StrEnum
Lifecycle status of a tenant.
State transitions::
PROVISIONING → ACTIVE → SUSPENDED → ACTIVE (reinstated)
ACTIVE → DELETED (soft-delete)
SUSPENDED → DELETED (soft-delete)
IsolationStrategy¶
IsolationStrategy ¶
Bases: StrEnum
Data-isolation strategy applied to tenant requests.
Strategies
SCHEMA: Each tenant owns a dedicated PostgreSQL/MSSQL schema.
search_path is set per-connection so unqualified table
references resolve to the correct schema automatically.
DATABASE: Each tenant owns a separate database (or SQLite file).
Strongest isolation; highest resource overhead.
RLS: All tenants share the same schema and tables. PostgreSQL
Row-Level Security policies enforce isolation at the engine
level, with WHERE tenant_id = :id as defence-in-depth.
HYBRID: Premium tenants use one strategy; standard tenants use
another. Controlled via TenancyConfig.premium_tenants.
ResolutionStrategy¶
ResolutionStrategy ¶
Bases: StrEnum
Method used to extract the tenant identifier from an HTTP request.
Strategies
HEADER: Read a dedicated HTTP header (default: X-Tenant-ID).
SUBDOMAIN: Extract the leftmost subdomain from the Host header.
PATH: Parse a fixed URL path prefix.
JWT: Decode a Bearer JWT and read a configured claim.
CUSTOM: Inject a user-supplied TenantResolver via
TenancyManager.
Protocols¶
TenantResolver¶
TenantResolver ¶
Bases: Protocol
Structural protocol for tenant resolution strategies.
Any object that exposes an async def resolve(request) -> Tenant
method satisfies this protocol and can be used as a custom resolver
without inheriting from any library class.
Example — custom cookie resolver::
class CookieTenantResolver:
def __init__(self, store: TenantStore) -> None:
self._store = store
async def resolve(self, request: Request) -> Tenant:
slug = request.cookies.get("X-Tenant")
if not slug:
raise TenantResolutionError("Cookie missing", strategy="cookie")
return await self._store.get_by_identifier(slug)
# Works without inheriting from BaseTenantResolver:
assert isinstance(CookieTenantResolver(store), TenantResolver)
resolve
async
¶
resolve(request: Request) -> Tenant
Resolve the current tenant from request.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
A FastAPI / Starlette |
required |
Returns:
| Type | Description |
|---|---|
Tenant
|
The resolved |
Raises:
| Type | Description |
|---|---|
TenantResolutionError
|
When the request does not carry enough information to identify a tenant. |
TenantNotFoundError
|
When the identifier matches no known tenant. |
Source code in src/fastapi_tenancy/core/types.py
IsolationProvider¶
IsolationProvider ¶
Bases: Protocol
Structural protocol for data isolation strategies.
Any object that exposes the four required async methods satisfies this
protocol and can be injected into TenancyManager without inheriting
from BaseIsolationProvider.
Example — Redis keyspace isolation::
class RedisIsolationProvider:
async def get_session(self, tenant: Tenant): ... # yields RedisClient
async def apply_filters(self, query, tenant): ...
async def initialize_tenant(self, tenant): ...
async def destroy_tenant(self, tenant, **kw): ...