Skip to content

db

Here's the reference for the Database facade, the transaction helper, locking functions, many-to-many helpers, and row-watching utilities.

You can import them directly from fastapi_toolsets.db:

from fastapi_toolsets.db import (
    Database,
    LockMode,
    advisory_lock,
    lock_tables,
    m2m_add,
    m2m_remove,
    m2m_set,
    transaction,
    wait_for_row_change,
)

fastapi_toolsets.db.Database

One object that owns the engine, sessions, dependency, and middleware.

Provide exactly one of url (the facade builds and disposes the engine) or engine (an engine you own, e.g. for Alembic or event.listen, left untouched).

Parameters:

Name Type Description Default
url str | PostgresDsn | None

Database connection URL. Accepts a plain string or a Pydantic :class:~pydantic.PostgresDsn.

None
engine AsyncEngine | None

An existing :class:AsyncEngine to reuse instead of url.

None
session_class type[AsyncSession]

Session class for the sessionmaker (e.g. EventSession).

AsyncSession
expire_on_commit bool

Expire attributes after commit. Defaults to False.

False
autoflush bool

Autoflush the session before queries. Defaults to True.

True
connect_args dict[str, Any] | None

DBAPI-level connection arguments forwarded to :func:create_async_engine (URL mode only).

None
**engine_options Any

Extra keyword arguments forwarded to :func:create_async_engine (URL mode only).

{}

Raises:

Type Description
TypeError

If neither or both of url and engine are given, or if connect_args/engine_options are passed together with engine.

Example
from fastapi import Depends, FastAPI
from fastapi_toolsets.db import Database

db = Database("postgresql+asyncpg://postgres:postgres@localhost/app")

app = FastAPI()
db.install(app)

@app.get("/users/{user_id}")
async def get_user(user_id: int, session=Depends(db)):
    return await UserCrud.get(session, [User.id == user_id])

__call__(request) async

FastAPI dependency: yield a session and commit once at the right time.

Parameters:

Name Type Description Default
request Request

The incoming request (injected by FastAPI).

required

Yields:

Type Description
AsyncGenerator[AsyncSession, None]

An AsyncSession for the duration of the request.

Example
@app.get("/users/{user_id}")
async def get_user(user_id: int, session=Depends(db)):
    return await UserCrud.get(session, [User.id == user_id])

begin() async

Open a session already inside a transaction (sugar for the common case).

Equivalent to session() + :func:transaction. Commits on clean exit, rolls back on exception.

Yields:

Type Description
AsyncGenerator[AsyncSession, None]

An AsyncSession open within a transaction.

Example
async with db.begin() as session:
    session.add(User(name="ada"))

install(app)

Wire the commit middleware and engine disposal onto app.

Parameters:

Name Type Description Default
app Any

The FastAPI/Starlette application to wire.

required
Example
@asynccontextmanager
async def lifespan(app):
    ...  # your startup
    yield
    ...  # your shutdown

app = FastAPI(lifespan=lifespan)
db.install(app)

lifespan(app) async

Dispose the engine on shutdown; use as FastAPI(lifespan=db.lifespan).

Parameters:

Name Type Description Default
app Any

The ASGI application (unused; required by the lifespan protocol).

required

Yields:

Type Description
AsyncGenerator[None, None]

Control to the application for its lifetime.

Example
app = FastAPI(lifespan=db.lifespan)

lock_tables(tables, *, mode=LockMode.SHARE_UPDATE_EXCLUSIVE, timeout='5s')

Lock PostgreSQL tables for the duration of a dedicated transaction.

Opens its own session from the facade's sessionmaker, changes are committed when the context exits.

Parameters:

Name Type Description Default
tables list[type[DeclarativeBase]]

List of SQLAlchemy model classes to lock.

required
mode LockMode

Lock mode (default: SHARE UPDATE EXCLUSIVE).

SHARE_UPDATE_EXCLUSIVE
timeout str

Lock timeout (default: "5s").

'5s'

Yields:

Type Description
AbstractAsyncContextManager[AsyncSession]

The dedicated session, open within the locked transaction.

Raises:

Type Description
LockTimeoutError

If the lock cannot be acquired within timeout.

PoolExhaustedError

If the connection pool is exhausted.

Example
async with db.lock_tables([User, Account]) as session:
    user = await UserCrud.get(session, [User.id == 1])
    user.balance += 100

session() async

Open a session outside request handlers (background tasks, CLI, tests).

Commits on clean exit, rolls back on exception.

Yields:

Type Description
AsyncGenerator[AsyncSession, None]

An AsyncSession ready for database operations.

Example
async with db.session() as session:
    user = await UserCrud.get(session, [User.id == 1])

fastapi_toolsets.db.transaction(session) async

Run a block inside a savepoint-aware transaction.

If session is already in a transaction, a nested transaction (savepoint) is opened so the block can roll back independently. Otherwise a top-level transaction is started. Commits on clean exit, rolls back on exception.

Parameters:

Name Type Description Default
session AsyncSession

AsyncSession instance.

required

Yields:

Type Description
AsyncGenerator[AsyncSession, None]

The session within the transaction context.

Example
from fastapi_toolsets.db import transaction

async with transaction(session):
    session.add(model)

fastapi_toolsets.db.LockMode

Bases: str, Enum

PostgreSQL table lock modes.

See: https://www.postgresql.org/docs/current/explicit-locking.html

fastapi_toolsets.db.lock_tables(session_maker, tables, *, mode=LockMode.SHARE_UPDATE_EXCLUSIVE, timeout='5s')

Lock PostgreSQL tables for the duration of a transaction.

Prefer the method on a :class:Database instance; use this directly only when you manage your own session factory.

Parameters:

Name Type Description Default
session_maker async_sessionmaker[_SessionT]

Async session factory used to create the dedicated session.

required
tables list[type[DeclarativeBase]]

List of SQLAlchemy model classes to lock.

required
mode LockMode

Lock mode (default: SHARE UPDATE EXCLUSIVE).

SHARE_UPDATE_EXCLUSIVE
timeout str

Lock timeout (default: "5s").

'5s'

Yields:

Type Description
AbstractAsyncContextManager[_SessionT]

The dedicated session, open within the locked transaction.

Raises:

Type Description
LockTimeoutError

If the lock cannot be acquired within timeout.

PoolExhaustedError

If the connection pool is exhausted.

Example
from fastapi_toolsets.db import lock_tables

async with lock_tables(session_maker, [User, Account]) as session:
    user = await UserCrud.get(session, [User.id == 1])
    user.balance += 100

fastapi_toolsets.db.advisory_lock(session, key, *, shared=False, nowait=False, timeout=None) async

Acquire a PostgreSQL session-level advisory lock.

Parameters:

Name Type Description Default
session AsyncSession

AsyncSession instance.

required
key int | tuple[int, int]

Lock key, either a single int (bigint) or a (int, int) pair for namespacing.

required
shared bool

Acquire a shared lock (multiple holders allowed). Default is exclusive.

False
nowait bool

Return False immediately if the lock is unavailable instead of waiting.

False
timeout str | None

Maximum wait time (e.g. "5s", "500ms"). Raises DBAPIError if exceeded. Ignored when nowait is True.

None

Yields:

Type Description
AsyncGenerator[bool, None]

True if the lock was acquired, False if nowait is True and the lock

AsyncGenerator[bool, None]

is already held.

Raises:

Type Description
LockTimeoutError

If timeout is set and the lock cannot be acquired in time.

Example
from fastapi_toolsets.db import advisory_lock

async with advisory_lock(session, 42):
    ...

async with advisory_lock(session, 42, nowait=True) as acquired:
    if not acquired:
        raise HTTPException(409, "Resource is locked")

async with advisory_lock(session, 42, timeout="5s"):
    ...

async with advisory_lock(session, (1, user_id), shared=True):
    ...

fastapi_toolsets.db.m2m_add(session, instance, rel_attr, *related, ignore_conflicts=False) async

Insert rows into a Many-to-Many association table without loading the ORM collection.

Parameters:

Name Type Description Default
session AsyncSession

DB async session.

required
instance DeclarativeBase

The "owner" side model instance (e.g. the A in A.b_list).

required
rel_attr QueryableAttribute

The M2M relationship attribute on the model class (e.g. A.b_list).

required
*related DeclarativeBase

One or more related instances to associate with instance.

()
ignore_conflicts bool

When True, silently skip rows that already exist in the association table (ON CONFLICT DO NOTHING).

False

Raises:

Type Description
TypeError

If rel_attr is not a Many-to-Many relationship.

Example
from fastapi_toolsets.db import m2m_add, transaction

async with transaction(session):
    await m2m_add(session, post, Post.tags, tag1, tag2)

fastapi_toolsets.db.m2m_remove(session, instance, rel_attr, *related) async

Remove rows from a Many-to-Many association table without loading the ORM collection.

Parameters:

Name Type Description Default
session AsyncSession

DB async session.

required
instance DeclarativeBase

The "owner" side model instance (e.g. the A in A.b_list).

required
rel_attr QueryableAttribute

The M2M relationship attribute on the model class (e.g. A.b_list).

required
*related DeclarativeBase

One or more related instances to disassociate from instance.

()

Raises:

Type Description
TypeError

If rel_attr is not a Many-to-Many relationship.

Example
from fastapi_toolsets.db import m2m_remove, transaction

async with transaction(session):
    await m2m_remove(session, post, Post.tags, tag1)

fastapi_toolsets.db.m2m_set(session, instance, rel_attr, *related) async

Replace the entire Many-to-Many association set atomically.

Parameters:

Name Type Description Default
session AsyncSession

DB async session.

required
instance DeclarativeBase

The "owner" side model instance (e.g. the A in A.b_list).

required
rel_attr QueryableAttribute

The M2M relationship attribute on the model class (e.g. A.b_list).

required
*related DeclarativeBase

The new complete set of related instances.

()

Raises:

Type Description
TypeError

If rel_attr is not a Many-to-Many relationship.

Example
from fastapi_toolsets.db import m2m_set, transaction

async with transaction(session):
    await m2m_set(session, post, Post.tags, tag1, tag2)  # replaces all

fastapi_toolsets.db.wait_for_row_change(session, model, pk_value, *, columns=None, interval=0.5, timeout=None) async

Poll a database row until a change is detected.

Queries the row every interval seconds and returns the model instance once a change is detected in any column (or only the specified columns).

Parameters:

Name Type Description Default
session AsyncSession

AsyncSession instance.

required
model type[_M]

SQLAlchemy model class.

required
pk_value Any

Primary key value of the row to watch.

required
columns list[str] | None

Optional list of column names to watch. If None, all columns are watched.

None
interval float

Polling interval in seconds (default: 0.5).

0.5
timeout float | None

Maximum time to wait in seconds. None means wait forever.

None

Returns:

Type Description
_M

The refreshed model instance with updated values.

Raises:

Type Description
NotFoundError

If the row does not exist or is deleted during polling.

TimeoutError

If timeout expires before a change is detected.

Example
from fastapi_toolsets.db import wait_for_row_change

# Wait for any column to change
updated = await wait_for_row_change(session, User, user_id)

# Watch specific columns with a timeout
updated = await wait_for_row_change(
    session, User, user_id,
    columns=["status", "email"],
    interval=1.0,
    timeout=30.0,
)

Admin and test helpers live in fastapi_toolsets.db.testing:

from fastapi_toolsets.db.testing import cleanup_tables, create_database

fastapi_toolsets.db.testing.create_database(db_name, *, server_url) async

Create a database.

Connects to server_url using AUTOCOMMIT isolation and issues a CREATE DATABASE statement for db_name.

Parameters:

Name Type Description Default
db_name str

Name of the database to create.

required
server_url str

URL used for server-level DDL (must point to an existing database on the same server).

required
Example
from fastapi_toolsets.db.testing import create_database

SERVER_URL = "postgresql+asyncpg://postgres:postgres@localhost/postgres"
await create_database("myapp_test", server_url=SERVER_URL)

fastapi_toolsets.db.testing.cleanup_tables(session, base) async

Truncate all tables for fast between-test cleanup.

Executes a single TRUNCATE … RESTART IDENTITY CASCADE statement across every table in base's metadata.

This is a no-op when the metadata contains no tables.

Parameters:

Name Type Description Default
session AsyncSession

An active async database session.

required
base type[DeclarativeBase]

SQLAlchemy DeclarativeBase class containing model metadata.

required
Example
@pytest.fixture
async def db_session(worker_db_url):
    async with create_db_session(worker_db_url, Base) as session:
        yield session
        await cleanup_tables(session, Base)