Multi-Tenancy
lexigram-tenancy adds first-class multi-tenancy — identifying the current tenant, isolating its data, and propagating its context safely across async calls.
1. Three Pillars
Section titled “1. Three Pillars”- Resolution — determine which tenant a request belongs to.
- Isolation — separate tenant data (row, schema, or database).
- Enforcement — bind the current context to the resolved tenant and reject requests that violate it.
2. Tenant Resolution
Section titled “2. Tenant Resolution”Resolution runs at the edge of the request pipeline. Resolvers are tried in order; the first match wins.
| Resolver | Source | Use case |
|---|---|---|
header | X-Tenant-ID header | API integrations, mobile apps |
jwt_claim | a claim in the auth token | OAuth2 / OIDC requests |
subdomain | tenant.app.com | classic SaaS |
path | /api/v1/{tenant}/... | multi-org public portals |
from lexigram import Applicationfrom lexigram.tenancy import TenancyModule, TenancyConfig, ResolutionConfig
app = Application(name="my-saas")app.add_module( TenancyModule.configure( TenancyConfig(resolution=ResolutionConfig(resolvers=["header", "jwt_claim"])) ))For tests, TenancyModule.stub() provides an in-memory, header-only setup with no isolation overhead.
3. Context Propagation
Section titled “3. Context Propagation”Once resolved, a TenantContextMiddleware stores the tenant id in a ContextVar, so it follows your code across await boundaries without being threaded through every function signature. Tenant-aware services and repositories read the current tenant from that context automatically.
See the lexigram-tenancy package docs for the exact context-accessor and tenant-scoping decorator APIs.
4. Data Isolation Strategies
Section titled “4. Data Isolation Strategies”| Strategy | How | Trade-off |
|---|---|---|
| Row-level (shared table) | every row carries a tenant_id | cheapest; relies on consistent filtering |
| Schema (shared DB) | one Postgres schema per tenant | stronger isolation, moderate ops overhead |
| Database (separate DBs) | a database per tenant | strongest isolation; highest ops cost |
The isolation strategy is pluggable per tenant via the package’s strategy registry.
5. Enforcement
Section titled “5. Enforcement”Mark routes as tenant-scoped so a request without a resolved, authorized tenant is rejected (401 if no tenant is present, 403 if the user doesn’t belong to it). The tenancy middleware validates the resolved tenant before the handler runs.
Next Steps
Section titled “Next Steps”- Authentication — pairing tenants with JWT claims
- Database & Persistence — tenant-aware repositories
lexigram-tenancypackage — resolvers, isolation, and lifecycle