Skip to content

TenancyConfig

TenancyConfig

Bases: BaseSettings

Central configuration for the fastapi-tenancy library.

Instances are validated eagerly: invalid field values and inconsistent field combinations both raise ValidationError at construction time.

Example — programmatic::

Text Only
config = TenancyConfig(
    database_url="postgresql+asyncpg://user:pass@localhost/myapp",
    resolution_strategy="header",
    isolation_strategy="schema",
)

Example — environment variables::

Text Only
# .env
TENANCY_DATABASE_URL=postgresql+asyncpg://user:pass@localhost/myapp
TENANCY_RESOLUTION_STRATEGY=subdomain
TENANCY_DOMAIN_SUFFIX=.example.com
TENANCY_ISOLATION_STRATEGY=rls

config = TenancyConfig()  # reads from environment / .env

get_schema_name

Python
get_schema_name(tenant_identifier: str) -> str

Compute the PostgreSQL schema name for tenant_identifier.

The identifier is validated against tenant slug rules before use to prevent SQL injection via schema names.

Parameters:

Name Type Description Default
tenant_identifier str

The tenant's human-readable slug.

required

Returns:

Type Description
str

Schema name string (e.g. "tenant_acme_corp").

Raises:

Type Description
ValueError

When tenant_identifier fails validation.

Example::

Text Only
config.get_schema_name("acme-corp")  # → "tenant_acme_corp"
Source code in src/fastapi_tenancy/core/config.py
Python
def get_schema_name(self, tenant_identifier: str) -> str:
    """Compute the PostgreSQL schema name for *tenant_identifier*.

    The identifier is validated against tenant slug rules before use to
    prevent SQL injection via schema names.

    Args:
        tenant_identifier: The tenant's human-readable slug.

    Returns:
        Schema name string (e.g. ``"tenant_acme_corp"``).

    Raises:
        ValueError: When *tenant_identifier* fails validation.

    Example::

        config.get_schema_name("acme-corp")  # → "tenant_acme_corp"
    """
    from fastapi_tenancy.utils.validation import validate_tenant_identifier  # noqa: PLC0415

    if not validate_tenant_identifier(tenant_identifier):
        msg = f"Invalid tenant identifier for schema name: {tenant_identifier!r}"
        raise ValueError(msg)
    sanitised = tenant_identifier.replace("-", "_").replace(".", "_")
    return f"{self.schema_prefix}{sanitised}"

get_database_url_for_tenant

Python
get_database_url_for_tenant(tenant_id: str) -> str

Build the database URL for tenant_id in DATABASE isolation mode.

Parameters:

Name Type Description Default
tenant_id str

The tenant's opaque unique ID.

required

Returns:

Type Description
str

A fully-qualified async database URL string.

Source code in src/fastapi_tenancy/core/config.py
Python
def get_database_url_for_tenant(self, tenant_id: str) -> str:
    """Build the database URL for *tenant_id* in DATABASE isolation mode.

    Args:
        tenant_id: The tenant's opaque unique ID.

    Returns:
        A fully-qualified async database URL string.
    """
    if self.database_url_template:
        db_name = tenant_id.replace("-", "_").replace(".", "_").lower()
        return self.database_url_template.format(
            tenant_id=tenant_id,
            database_name=f"tenant_{db_name}_db",
        )
    return str(self.database_url)

is_premium_tenant

Python
is_premium_tenant(tenant_id: str) -> bool

Return True if tenant_id is in the premium-tenants list.

Uses an internal frozenset built at construction time for O(1) lookup — the premium_tenants list may contain thousands of IDs and this method is called on every request in HYBRID mode.

Parameters:

Name Type Description Default
tenant_id str

Tenant ID to check.

required

Returns:

Type Description
bool

True for premium tenants; False for standard tenants.

Source code in src/fastapi_tenancy/core/config.py
Python
def is_premium_tenant(self, tenant_id: str) -> bool:
    """Return ``True`` if *tenant_id* is in the premium-tenants list.

    Uses an internal ``frozenset`` built at construction time for O(1)
    lookup — the ``premium_tenants`` list may contain thousands of IDs and
    this method is called on every request in HYBRID mode.

    Args:
        tenant_id: Tenant ID to check.

    Returns:
        ``True`` for premium tenants; ``False`` for standard tenants.
    """
    # _premium_set is built by _validate_cross_field_consistency at model
    # construction time.  Fallback to a fresh frozenset in case the method
    # is called on a partially-initialised instance (e.g. in unit tests
    # that bypass the normal constructor path).
    premium_set: frozenset[str] = getattr(self, "_premium_set", frozenset(self.premium_tenants))
    return tenant_id in premium_set

get_isolation_strategy_for_tenant

Python
get_isolation_strategy_for_tenant(
    tenant_id: str,
) -> IsolationStrategy

Return the effective isolation strategy for tenant_id.

In HYBRID mode this dispatches to premium_isolation_strategy or standard_isolation_strategy based on the tenant's tier. In all other modes it returns isolation_strategy unchanged.

Parameters:

Name Type Description Default
tenant_id str

Tenant ID to evaluate.

required

Returns:

Type Description
IsolationStrategy

The IsolationStrategy to apply.

Source code in src/fastapi_tenancy/core/config.py
Python
def get_isolation_strategy_for_tenant(self, tenant_id: str) -> IsolationStrategy:
    """Return the effective isolation strategy for *tenant_id*.

    In ``HYBRID`` mode this dispatches to ``premium_isolation_strategy``
    or ``standard_isolation_strategy`` based on the tenant's tier.  In
    all other modes it returns ``isolation_strategy`` unchanged.

    Args:
        tenant_id: Tenant ID to evaluate.

    Returns:
        The ``IsolationStrategy`` to apply.
    """
    if self.isolation_strategy != IsolationStrategy.HYBRID:
        return self.isolation_strategy
    return (
        self.premium_isolation_strategy
        if self.is_premium_tenant(tenant_id)
        else self.standard_isolation_strategy
    )