Advanced Repository#

This section covers advanced repository features including composite primary keys and row locking.

Composite Primary Keys#

Advanced Alchemy supports models with composite primary keys. For these models, the repository methods accept several formats for identifying records.

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

from advanced_alchemy.base import BigIntBase, DefaultBase
from advanced_alchemy.repository import SQLAlchemyAsyncRepository


class AdvancedUser(BigIntBase):
    __tablename__ = "advanced_user_account"

    username: Mapped[str]


class AdvancedRole(BigIntBase):
    __tablename__ = "advanced_role"

    name: Mapped[str]


class AdvancedUserRole(DefaultBase):
    __tablename__ = "advanced_user_role"

    user_id: Mapped[int] = mapped_column(ForeignKey("advanced_user_account.id"), primary_key=True)
    role_id: Mapped[int] = mapped_column(ForeignKey("advanced_role.id"), primary_key=True)
    permissions: Mapped[str] = mapped_column(default="member")


class AdvancedPost(BigIntBase):
    __tablename__ = "advanced_post"

    title: Mapped[str]
    published: Mapped[bool] = mapped_column(default=False)


class AdvancedUserRoleRepository(SQLAlchemyAsyncRepository[AdvancedUserRole]):
    model_type = AdvancedUserRole


class AdvancedUserRepository(SQLAlchemyAsyncRepository[AdvancedUser]):
    model_type = AdvancedUser


class AdvancedPostRepository(SQLAlchemyAsyncRepository[AdvancedPost]):
    model_type = AdvancedPost

Tuple Format

Pass primary key values as a tuple in the order they are defined on the model.

async def get_user_role_by_tuple(
    db_session: AsyncSession,
    user_id: int,
    role_id: int,
) -> AdvancedUserRole:
    repository = AdvancedUserRoleRepository(session=db_session)
    return await repository.get((user_id, role_id))

Dict Format

Pass primary key values as a dictionary with column names as keys. This is more explicit and avoids ordering issues.

async def get_user_role_by_mapping(
    db_session: AsyncSession,
    user_id: int,
    role_id: int,
) -> AdvancedUserRole:
    repository = AdvancedUserRoleRepository(session=db_session)
    return await repository.get({"user_id": user_id, "role_id": role_id})

Bulk Operations

You can use sequences of tuples or dicts for bulk operations like delete_many.

async def delete_user_roles(
    db_session: AsyncSession,
    role_ids: Sequence[dict[str, int]],
) -> Sequence[AdvancedUserRole]:
    repository = AdvancedUserRoleRepository(session=db_session)
    return await repository.delete_many(list(role_ids))

Row Locking (FOR UPDATE)#

Added in version 1.9.0.

The get_one and get_one_or_none methods support a with_for_update parameter, allowing you to emit a SELECT ... FOR UPDATE query for row-level locking.

async def get_user_for_update(db_session: AsyncSession, user_id: int) -> AdvancedUser:
    repository = AdvancedUserRepository(session=db_session)
    return await repository.get_one(id=user_id, with_for_update=True)


async def get_user_for_update_nowait(db_session: AsyncSession, user_id: int) -> AdvancedUser:
    repository = AdvancedUserRepository(session=db_session)
    return await repository.get_one(
        id=user_id,
        with_for_update={"nowait": True, "of": AdvancedUser.id},
    )

Custom DELETE WHERE#

For deleting multiple records matching a specific criteria:

async def delete_unpublished_posts(db_session: AsyncSession) -> Sequence[AdvancedPost]:
    repository = AdvancedPostRepository(session=db_session)
    return await repository.delete_where(AdvancedPost.published.is_(False))