Skip to main content

PostgreSQL RLS transparent multitenancy via contextvars for FastAPI + SQLAlchemy

Project description

ulfblk-multitenant

PostgreSQL Row-Level Security (RLS) transparent multitenancy for FastAPI + SQLAlchemy. The developer never writes tenant filtering code manually.

Part of Bloques Reciclables - an open source ecosystem of reusable code blocks.

Installation

uv add ulfblk-multitenant
# or
pip install ulfblk-multitenant

How It Works

  1. TenantMiddleware extracts tenant_id from the request (e.g., from JWT via auth middleware)
  2. TenantContext stores it in a contextvar (async-safe)
  3. SQLAlchemy event listener injects SET LOCAL app.current_tenant on each transaction
  4. PostgreSQL RLS policies filter rows automatically

The developer writes normal queries - RLS handles isolation transparently.

Quick Start

1. Generate RLS SQL

from ulfblk_multitenant.rls import generate_rls_sql

# Generate and execute in your migration
sql = generate_rls_sql("orders", tenant_column="tenant_id")
# Enables RLS, creates tenant_isolation policy

2. Configure SQLAlchemy

from sqlalchemy.ext.asyncio import create_async_engine
from ulfblk_multitenant.rls import apply_rls

engine = create_async_engine("postgresql+asyncpg://...")
apply_rls(engine)  # registers the event listener

3. Add Middleware

from fastapi import FastAPI
from ulfblk_multitenant.rls import TenantMiddleware

app = FastAPI()

# Default: reads tenant_id from request.state.tenant_id
app.add_middleware(TenantMiddleware)

# Or custom extractor:
app.add_middleware(
    TenantMiddleware,
    tenant_extractor=lambda req: req.headers.get("X-Tenant-ID"),
)

4. Use TenantContext Directly

from ulfblk_multitenant.context import get_current_tenant, set_current_tenant, clear_current_tenant

# Set manually (useful in background tasks, scripts)
ctx = set_current_tenant(
    tenant_id="acme-123",
    tenant_slug="acme",
    tenant_name="ACME Corp",
)

# Read current tenant
tenant = get_current_tenant()
if tenant:
    print(f"Current tenant: {tenant.tenant_id}")

# Clear
clear_current_tenant()

With ulfblk-auth

The typical setup combines ulfblk-auth JWT middleware with ulfblk-multitenant:

from fastapi import FastAPI
from ulfblk_core.middleware import RequestIDMiddleware
from ulfblk_multitenant.rls import TenantMiddleware, apply_rls

app = FastAPI()

# Middleware stack (order matters - last added runs first)
app.add_middleware(TenantMiddleware)  # 3. Set tenant context from JWT
# ... auth middleware sets request.state.tenant_id ...
app.add_middleware(RequestIDMiddleware)  # 1. Set request ID

# SQLAlchemy RLS
apply_rls(engine)

# Now all queries are automatically filtered by tenant
@app.get("/orders")
async def list_orders(session: AsyncSession = Depends(get_session)):
    result = await session.execute(select(Order))
    return result.scalars().all()  # only current tenant's orders

Dependencies

Requirements

  • Python 3.11+
  • PostgreSQL 14+ (RLS support)

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ulfblk_multitenant-0.1.0.tar.gz (4.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ulfblk_multitenant-0.1.0-py3-none-any.whl (6.0 kB view details)

Uploaded Python 3

File details

Details for the file ulfblk_multitenant-0.1.0.tar.gz.

File metadata

  • Download URL: ulfblk_multitenant-0.1.0.tar.gz
  • Upload date:
  • Size: 4.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ulfblk_multitenant-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0d97da7cd4f2048386cb0bc22c44fd28339d37701d4d56cc8b4775792b56536e
MD5 75375cae3a66aa3d15c5b7b916861eb7
BLAKE2b-256 a350259408d58122b68921ac57970200e6558814ceec71fd1dd4ab4e35478bfb

See more details on using hashes here.

File details

Details for the file ulfblk_multitenant-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: ulfblk_multitenant-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 6.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for ulfblk_multitenant-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 be832c020d3dd984732c9454bbe0cfcb6c4647bd0463f4149ff57719ef0588e5
MD5 4d3661acc38664fd390b4579997cf219
BLAKE2b-256 338649e57ceeb65b3a7201f1da3d8a4e441bca9c7d0339ff4a66d0a32b1f63a1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page