Lightweight async ORM on top of supabase-py with Pydantic validation.
Project description
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.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file supabase_orm-0.2.0.tar.gz.
File metadata
- Download URL: supabase_orm-0.2.0.tar.gz
- Upload date:
- Size: 225.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f49253149a1db0b99bb36c13f768ef5284ba22266d8e015f4369ee86f56f6f69
|
|
| MD5 |
575e6dd8f8c4c607bac145c652b7f47c
|
|
| BLAKE2b-256 |
4b6a06695223d8b4025cfded4c5efe9bf325b0b932997a7878f477e2ff160849
|
Provenance
The following attestation bundles were made for supabase_orm-0.2.0.tar.gz:
Publisher:
publish.yml on viperadnan-git/supabase-orm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
supabase_orm-0.2.0.tar.gz -
Subject digest:
f49253149a1db0b99bb36c13f768ef5284ba22266d8e015f4369ee86f56f6f69 - Sigstore transparency entry: 1571885064
- Sigstore integration time:
-
Permalink:
viperadnan-git/supabase-orm@4ba0ab5af32be3774429cab9949f49838555b458 -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/viperadnan-git
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4ba0ab5af32be3774429cab9949f49838555b458 -
Trigger Event:
push
-
Statement type:
File details
Details for the file supabase_orm-0.2.0-py3-none-any.whl.
File metadata
- Download URL: supabase_orm-0.2.0-py3-none-any.whl
- Upload date:
- Size: 57.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cd2b82bbd622c04cd855a1aa41d0fd8c7223643e684a2f88763ad45062f99dea
|
|
| MD5 |
7a10f05c3fe5341dd0dff6168ecc0732
|
|
| BLAKE2b-256 |
a51f51648519c91b68abf14c8b66753becc2e2825191137ec962e1ce233d035a
|
Provenance
The following attestation bundles were made for supabase_orm-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on viperadnan-git/supabase-orm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
supabase_orm-0.2.0-py3-none-any.whl -
Subject digest:
cd2b82bbd622c04cd855a1aa41d0fd8c7223643e684a2f88763ad45062f99dea - Sigstore transparency entry: 1571885076
- Sigstore integration time:
-
Permalink:
viperadnan-git/supabase-orm@4ba0ab5af32be3774429cab9949f49838555b458 -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/viperadnan-git
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4ba0ab5af32be3774429cab9949f49838555b458 -
Trigger Event:
push
-
Statement type: