Skip to main content

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.query is a real method with a real signature — autocomplete works, typos surface as AttributeError at call time, not as silent server-side errors.
  • Typed predicate expressions. Pet.f.age >= 5 builds a composable Predicate. 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 asyncio and FastAPI; a byte-for-byte sync API lives at supabase_orm.sync, generated via unasync.
  • PostgREST embeds, declared at the type level. Annotate a field as Annotated[Owner, Relation(...)] — the ORM builds the right select= string for you, including !inner / FK hints / per-relation filters.
  • Per-request RLS via ContextVar. Pair use_client() with a JWT-authenticated client in a FastAPI middleware — zero leakage between concurrent requests.
  • Typed RPC helpers. Call setof functions with row validation, get a single row, or coerce a scalar — one line each.
  • Foot-gun guards. Unfiltered bulk delete() / update() raise unless you pass allow_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 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.

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

supabase_orm-0.1.4.tar.gz (223.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

supabase_orm-0.1.4-py3-none-any.whl (56.7 kB view details)

Uploaded Python 3

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

Hashes for supabase_orm-0.1.4.tar.gz
Algorithm Hash digest
SHA256 1a58dad99ace9c3cfd5f37a686eeec75a04401e43098a8d3c880e250cd092112
MD5 a22e9a3b778e99ca4dda835ae486069e
BLAKE2b-256 dd278b5ef59bb8bc33ca3f7dcf9e8939c242e1aa87af55188509bbe2c297f269

See more details on using hashes here.

Provenance

The following attestation bundles were made for supabase_orm-0.1.4.tar.gz:

Publisher: publish.yml on viperadnan-git/supabase-orm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for supabase_orm-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 473296d9f60c9cd3e7e700cc27c416e34b13dc8868cb89f9337a5687189bfb46
MD5 e49bd2e6009796af5cd9a7a22df735ee
BLAKE2b-256 89d20a2dbb9e3a73e9f16102691afbde2822eef94f3ec047606ac68d2e944184

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page