supabase-orm
A lightweight, Pydantic-native ORM on top of supabase-py — async-first, sync mirror generated.
Documentation · Install · Quick start · Sync mode · Contributing
Features
- Pydantic models are tables. One class is your schema, validator, and query entry point — no separate DTO layer.
- Typed query builder.
Model.query.eq(...).gte(...)autocompletes; typos raise at call time, not server-side. - Composable predicates.
Pet.f.age >= 5builds aPredicate; combine with|/&/~, pass to.or_()/.not_(). - Declarative embeds. Annotate
Annotated[Owner, Relation(...)]— the rightselect=string +!inner/ FK hints are inferred. - Keyset iteration.
async for pet in Pet.query.iter():— constant-time per batch, race-safe under concurrent writes, any table size. - Async + sync. Async-first for FastAPI; a byte-for-byte sync mirror at
supabase_orm.syncis generated viaunasync. - Typed RPC. Call PostgREST functions with row validation, single-row, or scalar coercion in one line.
- Per-request RLS.
use_client()with a JWT-authenticated client in a FastAPI middleware — zero leakage between concurrent requests. - Safe by default. Unfiltered bulk
delete()/update()raise unless you opt in explicitly. - Battle-tested. 500+ mock tests for the wire contract; 80+ integration tests against real Supabase.
Install
uv add supabase-orm
# or
pip install supabase-orm
Requires Python 3.11+, supabase-py 2.30+, pydantic 2.13+.
Quick start
from uuid import UUID
from typing import Annotated
from supabase_orm import SupabaseModel, Relation, lifespan
class Owner(SupabaseModel, table="owners"):
id: UUID
email: str
is_active: bool
class Pet(SupabaseModel, table="pets"):
id: UUID
name: str
species: str
adopted: bool
owner: Annotated[Owner, Relation(join="inner")]
async with lifespan(SUPABASE_URL, SUPABASE_KEY):
# Chain-style query (sequential AND)
cats = await Pet.query.eq("species", "cat").order_by("-created_at").limit(10).all()
# Typed predicates (OR / NOT / boolean composition)
rescues = await Pet.query.or_(
Pet.f.species == "cat",
(Pet.f.species == "dog") & (Pet.f.adopted == False),
).all()
# Writes
p = await Pet.create(name="Whiskers", species="cat", adopted=False)
p.name = "Mr. Whiskers"
await p.save()
# Stream every matching row, any table size
async for pet in Pet.query.eq("adopted", False).iter():
await process(pet)
# Bulk update / delete (guards block unfiltered ops)
await Pet.query.eq("adopted", False).update(adopted=True)
Full guide: https://supabase-orm.readthedocs.io — models, predicates, embeds, lifecycle, RPC, extending.
Sync mode
Same model classes, same chain syntax, same predicates. Switch the import and drop await / async:
from supabase import create_client
from supabase_orm.sync import SupabaseModel, init, shutdown
class Pet(SupabaseModel, table="pets"):
id: UUID
name: str
species: str
init(create_client(SUPABASE_URL, SUPABASE_KEY))
cats = Pet.query.eq("species", "cat").limit(10).all()
for p in Pet.query.eq("species", "cat").iter():
process(p)
shutdown() # optional — process exit drains pools anyway
The sync tree is generated from the async source — no second implementation to keep in sync.
Contributing
git clone https://github.com/viperadnan-git/supabase-orm
cd supabase-orm
uv sync --all-groups
uv run pytest # mock suite (always runs)
Architecture
src/supabase_orm/_async/is the canonical implementation.src/supabase_orm/_sync/is generated byscripts/gen_sync.py(unasync-based token rewrite + prose regex + skip-block directive).- Tests mirror the same layout:
tests/_async/is the source,tests/_sync/is generated. - A pre-commit hook (
nizm) auto-regenerates and stages the sync mirror whenever_async/**/*.pychanges. CI also runspython scripts/gen_sync.py --checkto fail on drift.
Skip-block directive
Wrap async-only test code (e.g. concurrency tests using asyncio.gather) so the sync mirror omits it:
# gen_sync: skip-block
async def test_async_only():
...
# gen_sync: end-skip
Running tests
uv run pytest # mock only (default)
uv run pytest -m integration # live Supabase
Integration tests need a Supabase project with the test schema. See tests/integration/README.md.
Building docs locally
uv sync --group docs
uv run mkdocs serve # http://localhost:8000
License
Apache License 2.0 — see LICENSE.
Release files for supabase-orm 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| supabase_orm-0.2.2.tar.gz | 200.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| supabase_orm-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 258.8 kB
Release files / supabase_orm-0.2.2.tar.gz
| Download URL | supabase_orm-0.2.2.tar.gz |
|---|---|
| Size | 200.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4fa3e831b7ef62d58be8191c853fc2d12d3e5683447d24796899d82b0773f843
|
|
BLAKE2b-256 checksum How to use checksums |
b076e75fb62a8299d534074dcd95557a5d73583cf5f0ff9a623ec3269db29ca7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 13, 2026.
Transparency logRelease files / supabase_orm-0.2.2-py3-none-any.whl
| Download URL | supabase_orm-0.2.2-py3-none-any.whl |
|---|---|
| Size | 58.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a317bef87b39038a461c936ba6af61755a26658123ff8f50d67be9d06179c42a
|
|
BLAKE2b-256 checksum How to use checksums |
7d157de65d0268e2fdef925de8e45b03e54e57111c7757b56fd206a4f7d87332
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 13, 2026.
Transparency log