Skip to main content

sqlarec

sqlarec adds context-aware synchronous and asynchronous Active Record APIs to SQLAlchemy 2. Models can query and persist themselves without requiring every service function to receive and forward a session.

# Synchronous
user = User.query.where(User.email == email).one_or_none()

# Asynchronous
user = await AsyncUser.query.where(AsyncUser.email == email).one_or_none()

Your application still creates sessions and controls commits, rollbacks, and cleanup. sqlarec does not depend on a web framework and never commits inside a model method.

Install

Install the published package from PyPI:

uv add sqlarec

For async use, also install the driver for your database:

uv add aiosqlite          # SQLite
uv add "psycopg[binary]"  # PostgreSQL

sqlarec requires Python 3.11 or later and SQLAlchemy 2. It supports SQLAlchemy's synchronous Session and asynchronous AsyncSession.

Use the synchronous API

Import the synchronous API from sqlarec:

from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column

from sqlarec import BaseModel, init_engine, new_session


class User(BaseModel):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(100))
    email: Mapped[str] = mapped_column(String(255), unique=True)


engine = init_engine("sqlite:///:memory:")
BaseModel.metadata.create_all(engine)

session = new_session()
BaseModel.register_session_provider(lambda: session)

user = User.create(name="Hamza", email="hamza@example.com")
session.commit()

print(User.query.one().email)

Use the asynchronous API

Import the asynchronous API from sqlarec.asyncio. Async models use a separate declarative base and metadata registry:

import asyncio

from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column

from sqlarec.asyncio import (
    AsyncBaseModel,
    init_async_engine,
    new_async_session,
)


class User(AsyncBaseModel):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(100))
    email: Mapped[str] = mapped_column(String(255), unique=True)


async def main() -> None:
    engine = init_async_engine("sqlite+aiosqlite:///:memory:")

    async with engine.begin() as connection:
        await connection.run_sync(AsyncBaseModel.metadata.create_all)

    async with new_async_session() as session:
        AsyncBaseModel.register_session_provider(lambda: session)

        user = await User.create(name="Hamza", email="hamza@example.com")
        await session.commit()

        print((await User.query.one()).email)

    await engine.dispose()


asyncio.run(main())

Both quickstarts print:

hamza@example.com

Keep sessions at the application boundary

Register a zero-argument session provider during application setup. A command runner, job worker, dependency scope, or middleware can then bind the current session while models resolve it only when an operation executes.

For concurrent async applications, use a context-local provider and create one AsyncSession per task:

from contextvars import ContextVar

from sqlalchemy.ext.asyncio import AsyncSession

from sqlarec.asyncio import AsyncBaseModel, new_async_session

current_session = ContextVar[AsyncSession]("current_session")
AsyncBaseModel.register_session_provider(current_session.get)


async def database_middleware(request, handler):
    async with new_async_session() as session:
        token = current_session.set(session)
        try:
            response = await handler(request)
            await session.commit()
            return response
        except Exception:
            await session.rollback()
            raise
        finally:
            current_session.reset(token)

Code inside that boundary uses models without accepting a session:

async def find_user(email: str) -> User | None:
    return await User.query.where(User.email == email).one_or_none()

The same principle applies to the synchronous API with Session.

Query models and rows

Query-building methods are immutable and do not perform I/O. Result methods are synchronous or awaitable according to the selected base:

Operation Synchronous Asynchronous
All models User.query.all() await User.query.all()
One model User.query.one_or_none() await User.query.one_or_none()
Selected rows User.select(User.id).all() await User.select(User.id).all()
Mappings User.select(User.id).mappings() await User.select(User.id).mappings()

Builders include where(), filter_by(), order_by(), group_by(), having(), join(), outerjoin(), limit(), offset(), distinct(), options(), union(), and union_all().

Write without hidden commits

Write helpers flush the current session but never commit:

Operation Synchronous Asynchronous
Create User.create(...) await User.create(...)
Save user.save() await user.save()
Delete user.delete() await user.delete()
Primary-key lookup User.get_by_pk(42) await User.get_by_pk(42)
Update User.update().values(...).execute() await User.update().values(...).execute()

Keeping commits outside model methods lets the application commit or roll back a complete unit of work atomically. Models also expose User.session, and every query/update wrapper exposes its underlying .statement for direct SQLAlchemy use.

Async models include SQLAlchemy's AsyncAttrs. Prefer eager relationship loading, or use await model.awaitable_attrs.relationship, when attribute access would otherwise require implicit database I/O.

Develop the library

Clone the repository and install all development dependencies:

uv sync
Command Purpose
make test Run synchronous and asynchronous tests.
make lint Check source and tests with Ruff.
make typecheck Check source and tests with Astral ty.
make format Format source and tests with Ruff.
make clean Remove Python, pytest, and Ruff caches.

Current limitations

  • Sync and async models use separate declarative bases and metadata registries.
  • Each concurrent task must use its own AsyncSession.
  • Async database drivers are selected and installed by the application.
  • Query wrappers cover common operations; use .statement and the resolved session for advanced SQLAlchemy features.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sqlarec-0.2.2.tar.gz (31.3 kB view details)

Uploaded Source

Built Distribution

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

sqlarec-0.2.2-py3-none-any.whl (17.9 kB view details)

Uploaded Python 3

File details

Details for the file sqlarec-0.2.2.tar.gz.

File metadata

  • Download URL: sqlarec-0.2.2.tar.gz
  • Upload date:
  • Size: 31.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.1

File hashes

Hashes for sqlarec-0.2.2.tar.gz
Algorithm Hash digest
SHA256 886ebfbaf3d6d78872333d99d643e7c4bc4e6296553f2ef30061d89c005cc983
MD5 3c5a4339f4d85cc57f215f4de3e2fa6c
BLAKE2b-256 8114d9a5bcd77dd21826e20f140998645b8183f37bb8db6d8e04a9f34daab9a9

See more details on using hashes here.

File details

Details for the file sqlarec-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: sqlarec-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 17.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.1

File hashes

Hashes for sqlarec-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 2b83e8dce5fcd2ea0d6ef91f8e44963427444f98470f68ad9ebf1e7f3ecb22c7
MD5 adf54d9e2c126502c4a84c8952877060
BLAKE2b-256 77b17ae4bb3f696703ee70d44ced65ad8f3d4a213d29527024161eb2b061c115

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.0

2 files

0.4.3

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

This release

0.2.2 This release

2 files

0.2.1

2 files

0.2.0

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 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