How-To Guides
How do I resolve tenants from an HTTP header?
Section titled “How do I resolve tenants from an HTTP header?”from lexigram.tenancy import TenancyConfig, ResolutionConfig
config = TenancyConfig( resolution=ResolutionConfig( resolvers=["header"], header_name="x-tenant-id", ),)How do I add a custom resolver?
Section titled “How do I add a custom resolver?”from lexigram.contracts.tenancy.protocols import TenantResolverProtocolfrom lexigram.contracts.tenancy.types import TenantResolutionContext
class QueryParamResolver: """Resolves tenant from ?tenant_id=... query parameter."""
name = "query_param" priority = 25
async def resolve(self, context: TenantResolutionContext) -> str | None: return context.headers.get("x-query-tenant-id")Register it by overriding ResolverRegistry in a custom provider.
How do I use a SQL backend instead of in-memory?
Section titled “How do I use a SQL backend instead of in-memory?”Install with optional SQL support:
uv add lexigram-tenancy[sql]Then register SQLTenantProvider before the lifecycle provider runs:
from lexigram.tenancy.stores.sql import SQLTenantProvider
# Register before adding TenancyModulecontainer.singleton(TenantProviderProtocol, SQLTenantProvider(dsn=...))How do I configure per-tenant feature overrides?
Section titled “How do I configure per-tenant feature overrides?”from lexigram.tenancy.config_overrides.service import TenantConfigService
service = await container.resolve(TenantConfigService)await service.set_config("tenant-abc", "max_users", "100")value = await service.get_config("tenant-abc", "max_users", default="10")How do I switch to schema-based isolation?
Section titled “How do I switch to schema-based isolation?”from lexigram.tenancy import TenancyConfig, LifecycleConfig
config = TenancyConfig( lifecycle=LifecycleConfig(isolation_strategy="schema"),)Requires lexigram-sql and a Postgres database.
How do I run a query scoped to the current tenant?
Section titled “How do I run a query scoped to the current tenant?”from lexigram.primitives.context import TENANT_ID, Context
ctx = await container.resolve(Context)tenant_id = ctx.get(TENANT_ID)# Use tenant_id in SQL WHERE clauseHow do I use TenantGuard for route-level enforcement?
Section titled “How do I use TenantGuard for route-level enforcement?”from lexigram.tenancy import TenantGuardfrom lexigram.web.controllers import use_guards
# Apply to a whole controller@use_guards(TenantGuard)class BillingController: async def list_invoices(self) -> list[Invoice]: ...
# Apply to a single routeclass PublicController: @use_guards(TenantGuard) async def create_order(self, ...) -> Order: ...The guard checks that a valid ACTIVE tenant is present in the current
request context. Requests without an active tenant receive a 403 response.
How do I use TenantProvisioner for isolation setup?
Section titled “How do I use TenantProvisioner for isolation setup?”from lexigram.tenancy import TenantProvisionerfrom lexigram.tenancy.isolation.row_level import RowLevelIsolationStrategy
strategy = RowLevelIsolationStrategy()provisioner = TenantProvisioner(strategy=strategy, auto_provision=True)
# Provision isolation for a new tenantresult = await provisioner.provision("tenant-xyz")if result.is_ok(): print("Tenant schema/namespace provisioned")
# Deprovision when tenant is removedawait provisioner.deprovision("tenant-xyz")Set auto_provision=False in tests to skip isolation setup entirely.
How do I cache tenant config lookups?
Section titled “How do I cache tenant config lookups?”from lexigram.tenancy import CachedTenantConfigProviderfrom lexigram.tenancy.stores.memory import InMemoryTenantProvider
base = InMemoryTenantProvider()cached = CachedTenantConfigProvider(inner=base, ttl=60)
# First call fetches from the underlying providerawait cached.set_config("tenant-abc", "feature_x", "enabled")value = await cached.get_config("tenant-abc", "feature_x")print(value) # "enabled"
# Subsequent reads within the TTL use the in-memory cachecached_value = await cached.get_config("tenant-abc", "feature_x")
# Writes automatically invalidate the cache for that tenantawait cached.set_config("tenant-abc", "feature_x", "disabled")The cache stores the entire config dict per tenant. A write to any key invalidates that tenant’s cached dict so the next read is fresh.
How do I create a tenant programmatically?
Section titled “How do I create a tenant programmatically?”from lexigram.contracts.tenancy.protocols import TenantProviderProtocolfrom lexigram.contracts.tenancy.commands import CreateTenantCommand
provider = await container.resolve(TenantProviderProtocol)result = await provider.create_tenant( CreateTenantCommand(slug="newco", name="NewCo Inc."))if result.is_ok(): tenant = result.unwrap()