Multi-tenant SaaS scaffolding for Python services.
omkit is the Python side of Omur Labs' shared service toolkit. It bundles the
boring-but-load-bearing pieces every tenant-isolated service needs: a Postgres
pool that enforces Row-Level Security per connection, a pluggable event bus
(Postgres LISTEN/NOTIFY or Valkey), session and settings stores, BYOK
encryption helpers, FastAPI observability middleware, and a cross-runtime
job-queue envelope that interoperates with the Go sibling.
omkit is not a framework and not a BaaS. Each primitive (asyncpg, structlog, OTel, streaq) is borrowed. The value is in the seams:
- RLS-per-connection discipline encoded in the pool — tenant context flows
from
TenantMiddlewarethrough every downstream primitive so policies actually apply at the DB. - Cross-runtime job envelope — a Python streaq worker and a Go Asynq worker exchange byte-identical job envelopes over a shared Valkey instance.
- Wire-compatible encryption + settings with
omkit-goso secrets written by one runtime decrypt on the other.
Closest neighbors (django-tenants, Faktory, Encore.dev, Supabase) each cover
one axis; none cover all three as a library.
- Status:
v0.2.0— internal API stable. - Python:
>=3.12 - License: Apache-2.0
- Sibling:
omkit-go— same primitives, same envelope contract, same RLS conventions.
Scope note: LLM provider abstraction (
omkit.provider) and LLM-output sanitization (omkit.sanitize) are being migrated to cortex (see #4). Expect deprecation shims before removal.
pip install omkitOptional extras:
| Extra | Adds | When you need it |
|---|---|---|
tracing |
opentelemetry-{api,sdk}, OTLP/HTTP exporter, FastAPI + httpx instrumentation |
Distributed tracing |
metrics |
prometheus-fastapi-instrumentator |
FastAPI request metrics |
dev |
fastapi, pytest, pytest-asyncio, respx |
Local development and tests |
pip install "omkit[tracing,metrics]"A minimal tenant-aware FastAPI service wired up with the pieces omkit gives you:
from fastapi import FastAPI
from omkit.dbpool import create_pool
from omkit.tenant import TenantMiddleware
from omkit.transport import (
configure_logging,
init_tracing,
instrument_fastapi,
mount_metrics,
mount_health_endpoints,
)
configure_logging()
init_tracing(service_name="my-service")
app = FastAPI()
app.add_middleware(TenantMiddleware)
instrument_fastapi(app)
mount_metrics(app)
mount_health_endpoints(app)
pool = await create_pool("postgresql://...") # role-scoped, RLS-awareTenant context is set by TenantMiddleware and read by everything downstream
(pool, event bus, logger, HTTP client, job envelope) via omkit.tenant.
| Module | What it does |
|---|---|
omkit.dbpool |
Asyncpg pool that sets a Postgres role per connection so RLS policies apply. |
omkit.sessions |
SessionStore protocol with Postgres and Redis backends. |
omkit.encryption |
AES-256-GCM settings encryption — generate_key, encrypt_value, decrypt_value, mask_secret. Wire-compatible with omkit-go/encryption. |
omkit.data (facade) |
Re-exports pool + session primitives in one place. |
| Module | What it does |
|---|---|
omkit.eventbus |
EventBus protocol (PostgresEventBus, RedisEventBus) with env-driven factory. |
omkit.valkey |
Async Redis/Valkey client factory. |
omkit.events |
Deprecated shim — imports from here warn; use omkit.eventbus. |
| Module | What it does |
|---|---|
omkit.jobqueue |
Envelope contract — wrap/unwrap for tenant-scoped tasks. Same wire format as omkit-go/jobqueue. |
omkit.jobqueue.streaq |
streaq worker helpers (make_worker, enqueue, mount_streaq_ui, Prometheus collector). Import explicitly. |
omkit.model_lifecycle |
ModelRegistry + ModelLifecycle ABC — lazy model loading with polling. |
omkit.syncnotifier |
SyncNotifier — enqueue outbound HTTP callbacks. |
| Module | What it does |
|---|---|
omkit.logging |
configure_logging() — structlog + JSON formatter. |
omkit.metrics |
mount_metrics() — Prometheus endpoint on a FastAPI app. |
omkit.tracing |
init_tracing(), instrument_fastapi() — OpenTelemetry over OTLP/HTTP. |
omkit.health |
mount_health_endpoints() — /health + /ready routes. |
omkit.resilience |
CircuitBreaker, @resilient decorator, CircuitOpen exception. |
omkit.quota |
Resource, Limits, Usage, Decision, check_upload, check_query. |
omkit.transport (facade) |
Re-exports the wire + observability set. |
| Module | What it does |
|---|---|
omkit.tenant |
Tenant context (require, current_or_none, request_id, bind) and ASGI/FastAPI middleware. |
omkit.config |
BaseServiceSettings — pydantic-settings base class for env-driven config. |
omkit.settings |
SettingsManager — async tenant-settings loader with polling. |
omkit.platform (facade) |
Re-exports config + lifecycle + sync notifier. |
| Module | What it does |
|---|---|
omkit.security (facade) |
SecurityEvent + write_security_event / log_security_event + sanitation re-exports (sanitation moving to cortex). |
omkit.cost |
record_cost, COST_UNITS_TOTAL Prometheus counter, TenantBucket enum. |
omkit.httpclient |
build_tenant_client() — httpx AsyncClient that injects tenant headers. |
omkit.cleanup |
Loop — coordinated periodic task runner with pg_try_advisory_lock (matches omkit-go/cleanup.Loop). |
omkit.auth |
Role enum, with_roles context, has_role/require_role, AuditEntry + write_audit_entry — parity with omkit-go/auth. |
omkit.featureflags |
Flag, StaticStore, PostgresStore (TTL cache), allowed(store, key) — parity with omkit-go/featureflags. |
omkit.scheduler |
Scheduler — DB-driven reconcile loop wrapping any cron backend; PgxProviderSource, Provider, CronDeriver. Parity with omkit-go/scheduler. |
| Module | What it does |
|---|---|
omkit.provider |
ProviderBase ABC + ProviderRegistry. Slated for migration to cortex — see #4. |
omkit.sanitize |
LLM-output sanitization. Slated for migration to cortex — see #4. |
omkit.internal holds private helpers (currently crypto.py). Not part of the
stable surface — do not import.
Five subpackages re-export grouped primitives. Prefer importing from a facade when you only need the "shape" of a concern; reach into a flat module when you need a less common symbol.
| Facade | Re-exports |
|---|---|
omkit.data |
Pool + session primitives |
omkit.platform |
BaseServiceSettings, SettingsManager, ModelLifecycle, ModelRegistry, SyncNotifier |
omkit.security |
Sanitation + log_security_event (does not re-export omkit.encryption) |
omkit.transport |
build_tenant_client, init_tracing, instrument_fastapi, mount_metrics, mount_health_endpoints, configure_logging, CircuitBreaker, resilient |
omkit.jobqueue |
Envelope, wrap, unwrap, ENVELOPE_VERSION, InvalidEnvelopeError (streaq helpers via omkit.jobqueue.streaq) |
omkit.jobqueue.Envelope is the same wire contract used by
omkit-go/jobqueue. A Python streaq worker and a Go Asynq worker can publish
and consume each other's jobs as long as they agree on ENVELOPE_VERSION and
share the tenant routing rules. Pair it with a shared Valkey instance and
either runtime can pick up either runtime's work.
git clone git@github.com:omurlabs/omkit-python.git
cd omkit-python
pip install -e ".[dev,tracing,metrics]"
# Unit tests (no external services)
pytest
# Integration tests against a real Postgres
./scripts/test-with-postgres.shTest layout mirrors the module layout; every public module and facade has a
matching tests/test_*.py.