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))