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
- Type-safe query builder. Every operator on
Model.queryis a real method with a real signature — autocomplete works, typos surface asAttributeErrorat call time, not as silent server-side errors. - Typed predicate expressions.
Pet.f.age >= 5builds a composablePredicate. Combine with|/&/~and pass to.or_()/.not_(). - Keyset iteration that scales.
async for pet in Pet.query.eq(...).iter():paginates by PK with constant-time-per-batch, race-safe under concurrent inserts/deletes. - Pydantic v2 throughout. Your model is the row schema, the response schema, and the request body schema. No DTO layer.
- Async-first, sync mirror included. Built for
asyncioand FastAPI; a byte-for-byte sync API lives atsupabase_orm.sync, generated viaunasync. - PostgREST embeds, declared at the type level. Annotate a field as
Annotated[Owner, Relation(...)]— the ORM builds the rightselect=string for you, including!inner/ FK hints / per-relation filters. - Per-request RLS via
ContextVar. Pairuse_client()with a JWT-authenticated client in a FastAPI middleware — zero leakage between concurrent requests. - Typed RPC helpers. Call
setoffunctions with row validation, get a single row, or coerce a scalar — one line each. - Foot-gun guards. Unfiltered bulk
delete()/update()raise unless you passallow_unfiltered=True. - Tested both ways. 500+ mock tests cover the wire contract; 80+ integration tests run 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: viperadnan-git.github.io/supabase-orm — 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.1.4.tar.gz.
File metadata
- Download URL: supabase_orm-0.1.4.tar.gz
- Upload date:
- Size: 223.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a58dad99ace9c3cfd5f37a686eeec75a04401e43098a8d3c880e250cd092112
|
|
| MD5 |
a22e9a3b778e99ca4dda835ae486069e
|
|
| BLAKE2b-256 |
dd278b5ef59bb8bc33ca3f7dcf9e8939c242e1aa87af55188509bbe2c297f269
|
Provenance
The following attestation bundles were made for supabase_orm-0.1.4.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.1.4.tar.gz -
Subject digest:
1a58dad99ace9c3cfd5f37a686eeec75a04401e43098a8d3c880e250cd092112 - Sigstore transparency entry: 1566468524
- Sigstore integration time:
-
Permalink:
viperadnan-git/supabase-orm@1d84dee20272a6d31188a123f3f26f805e14ccd5 -
Branch / Tag:
refs/tags/0.1.4 - Owner: https://github.com/viperadnan-git
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1d84dee20272a6d31188a123f3f26f805e14ccd5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file supabase_orm-0.1.4-py3-none-any.whl.
File metadata
- Download URL: supabase_orm-0.1.4-py3-none-any.whl
- Upload date:
- Size: 56.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 |
473296d9f60c9cd3e7e700cc27c416e34b13dc8868cb89f9337a5687189bfb46
|
|
| MD5 |
e49bd2e6009796af5cd9a7a22df735ee
|
|
| BLAKE2b-256 |
89d20a2dbb9e3a73e9f16102691afbde2822eef94f3ec047606ac68d2e944184
|
Provenance
The following attestation bundles were made for supabase_orm-0.1.4-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.1.4-py3-none-any.whl -
Subject digest:
473296d9f60c9cd3e7e700cc27c416e34b13dc8868cb89f9337a5687189bfb46 - Sigstore transparency entry: 1566468561
- Sigstore integration time:
-
Permalink:
viperadnan-git/supabase-orm@1d84dee20272a6d31188a123f3f26f805e14ccd5 -
Branch / Tag:
refs/tags/0.1.4 - Owner: https://github.com/viperadnan-git
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1d84dee20272a6d31188a123f3f26f805e14ccd5 -
Trigger Event:
push
-
Statement type: