Skip to main content

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 >= 5 builds a Predicate; combine with | / & / ~, pass to .or_() / .not_().
  • Declarative embeds. Annotate Annotated[Owner, Relation(...)] — the right select= 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.sync is generated via unasync.
  • 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 by scripts/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/**/*.py changes. CI also runs python scripts/gen_sync.py --check to 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)

Source distribution for supabase-orm 0.2.2
File Size Uploaded
supabase_orm-0.2.2.tar.gz 200.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for supabase-orm 0.2.2
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page