Skip to content

Client lifecycle

Pick the entry point that matches your runtime. All four manage the same ContextVar — the difference is who owns the client and when it's closed.

from supabase_orm import lifespan

async with lifespan(SUPABASE_URL, SUPABASE_KEY):
    # the orm sees the client for the duration of this block
    rows = await Pet.query.all()
from supabase import acreate_client
from supabase_orm import init, shutdown

init(await acreate_client(SUPABASE_URL, SUPABASE_KEY))
try:
    rows = await Pet.query.all()
finally:
    await shutdown()
from supabase import create_client
from supabase_orm.sync import init, shutdown

init(create_client(SUPABASE_URL, SUPABASE_KEY))
try:
    rows = Pet.query.all()
finally:
    shutdown()

lifespan async

lifespan(url: str, key: str) -> AsyncIterator[AsyncClient]

Context manager that owns one AsyncClient for its lifetime.

Builds the client, hands ownership to the orm via :func:init, drains pools on exit via :func:shutdown.

Source code in src/supabase_orm/_async/_client.py
@asynccontextmanager
async def lifespan(url: str, key: str) -> AsyncIterator[AsyncClient]:
    """Context manager that owns one ``AsyncClient`` for its lifetime.

    Builds the client, hands ownership to the orm via :func:`init`,
    drains pools on exit via :func:`shutdown`.
    """
    client = init(await acreate_client(url, key))
    try:
        yield client
    finally:
        await shutdown()

init

init(client: AsyncClient) -> AsyncClient

Bind client and transfer ownership: shutdown() will close it.

Returns client for inline composition::

init(await acreate_client(URL, KEY))
Source code in src/supabase_orm/_async/_client.py
def init(client: AsyncClient) -> AsyncClient:
    """Bind ``client`` and transfer ownership: ``shutdown()`` will close it.

    Returns ``client`` for inline composition::

        init(await acreate_client(URL, KEY))
    """
    global _owned_client
    _owned_client = client
    set_client(client)
    return client

shutdown async

shutdown() -> None

Unbind the app-wide client. Closes it if :func:init registered ownership.

Idempotent; no-op when nothing was bound. Subclient close failures are logged, not raised.

Source code in src/supabase_orm/_async/_client.py
async def shutdown() -> None:
    """Unbind the app-wide client. Closes it if :func:`init` registered ownership.

    Idempotent; no-op when nothing was bound. Subclient close failures
    are logged, not raised.
    """
    global _owned_client
    set_client(None)
    if _owned_client is None:
        return
    client, _owned_client = _owned_client, None
    # supabase-py's AsyncClient has no top-level close as of 2.x — drain
    # each subclient's httpx pool individually.
    for sub in ("postgrest", "auth", "storage", "functions"):
        obj = getattr(client, sub, None)
        close = getattr(obj, "aclose", None)
        if close is None:
            continue
        try:
            await close()
        except Exception as exc:  # noqa: BLE001
            _log.warning("supabase_orm: failed to close %s: %r", sub, exc)

set_client

set_client(client: AsyncClient | None) -> None

Bind client as the app-wide default; caller keeps ownership.

shutdown() unbinds but does not close. For ownership transfer use :func:init.

Source code in src/supabase_orm/_async/_client.py
def set_client(client: AsyncClient | None) -> None:
    """Bind ``client`` as the app-wide default; caller keeps ownership.

    ``shutdown()`` unbinds but does not close. For ownership transfer use
    :func:`init`.
    """
    global _default_client
    _default_client = client

get_client

get_client() -> AsyncClient
Source code in src/supabase_orm/_async/_client.py
def get_client() -> AsyncClient:
    c = _client_override.get() or _default_client
    if c is None:
        raise SupabaseORMUsageError(
            "Supabase AsyncClient not initialized. "
            "Wrap your app startup in `async with lifespan(url, key):` "
            "or call `set_client(client)` directly."
        )
    return c

use_client async

use_client(
    client: AsyncClient,
) -> AsyncIterator[AsyncClient]

Bind client for the duration of the async with block only.

Per-task isolated — concurrent requests don't see each other's overrides.

Source code in src/supabase_orm/_async/_client.py
@asynccontextmanager
async def use_client(client: AsyncClient) -> AsyncIterator[AsyncClient]:
    """Bind ``client`` for the duration of the ``async with`` block only.

    Per-task isolated — concurrent requests don't see each other's
    overrides.
    """
    token = _client_override.set(client)
    try:
        yield client
    finally:
        _client_override.reset(token)