API reference¶
Everything the package exposes, in one place. For task-oriented walkthroughs, start with the guides.
Imports¶
from repositron import (
Repository, # full CRUD generic base
ReadOnlyRepository, # read-only generic base
PaginatedResult, # the {items, total} container
OrderBy, # the order_by argument type
UNSET, UnsetType, # the partial-update sentinel and its type
on, # decorator: tag a method as a lifecycle hook
writes, # decorator: give a custom write flush/commit/rollback
)
Type parameters¶
Repository[Model, DTO = Model, Create = object, Update = object, PK = int]
ReadOnlyRepository[Model, DTO = Model, PK = int]
Model is required. DTO defaults to the model itself (reads return the model,
unhydrated). Create and Update are the payload dataclasses your writes
accept. PK is the primary-key type, defaulting to int; declare it (last,
after the others) when your key is a str or uuid. So Repository[Workspace]
is a valid read/write repository returning Workspace with an int key, and you
add the other parameters only as you need them. See
primary keys.
Class attributes¶
Set these on your repository subclass.
| Attribute | Type | Purpose | Default |
|---|---|---|---|
field_mapping |
dict[str, str] |
{model_column: dto_field} for renamed fields |
{} |
pk_column |
str \| InstrumentedAttribute |
primary-key column, by name or column reference | "id" |
class TaskRepository(Repository[Task, TaskDTO, TaskCreate, TaskUpdate]):
field_mapping = {"created_at": "opened_at"} # model column : DTO field
pk_column = Task.id # or "id"
Read methods¶
Available on both ReadOnlyRepository and Repository.
get(id) -> DTO | None- Fetch one row by primary key, hydrated to the DTO.
Noneif absent. first(*, extra_filters=None, order_by=None, **filters) -> DTO | None- The first row matching the filters, or
None. See filtering. list(*, extra_filters=None, order_by=None, **filters) -> list[DTO]- All rows matching the filters, each hydrated to the DTO.
list_paginated(offset, limit=20, *, extra_filters=None, order_by, **filters) -> PaginatedResult[DTO]- A page plus the unpaginated total.
order_byis required; omitting it raisesValueError. See pagination. count(*, extra_filters=None, **filters) -> int- Count of rows matching the filters.
exists(id) -> bool- Whether a row with this primary key exists.
repo[Shape]- A clone bound to
Shapefor the next call, triggering column projection whenShapeis a narrow dataclass. See projection.
Constructor¶
Repository(session, *, autocommit=False, rollback_on_error=True)ReadOnlyRepository(session, *, autocommit=False, rollback_on_error=True)sessionis the caller-owned SQLAlchemySession. Withautocommit=True, every write commits after its flush; the default flushes only and leaves the transaction to you.rollback_on_error(Trueby default) rolls the session back before re-raising when a flush or commit fails; set it toFalseto leave that rollback to you. Both bases take these: aReadOnlyRepositoryhas no automatic writes, but they govern any@writesmethod it hosts. See transactions.
Write methods¶
Available on Repository only. Each takes commit: bool | None = None: None
follows the instance's autocommit, True/False overrides it for that one
call. On a commit failure the session is rolled back and the error re-raised.
create(payload, *, commit=None) -> PK- Insert from a dataclass payload and flush.
UNSETfields are omitted so the column default applies. Returns the new primary key, typed as thePKparameter (intby default). update(id, payload, *, commit=None) -> bool- Partial-update from a dataclass payload and flush.
UNSETfields are skipped;Noneis written asNULL.Falseif no row has that key. See updates. delete(id, *, commit=None) -> bool- Delete by primary key and flush.
Falseif no row has that key.
Lifecycle hooks¶
Tag a method with @on(event, mode=...) to run it inside the base's
create / update / delete / hydration. Collected once per class; an unknown
event raises TypeError at import. See hooks.
@on(event, mode) |
receives | returns |
|---|---|---|
"create", "before" |
model, payload |
nothing |
"create", "after" |
model |
nothing |
"update", "before" |
model, payload |
nothing |
"update", "after" |
model |
nothing |
"delete", "before" |
model |
nothing |
"delete", "after" |
model |
nothing |
"hydrate", "build" |
model |
the DTO |
"hydrate", "after" |
model, dto |
the DTO |
before write hooks run before the flush; after run once the model has its
primary key. hydrate hooks fire on every read that hydrates, but not on
repo[Shape] projection. build is single-winner (most-derived wins); the
others all run, base classes before subclasses.
Custom hydration¶
_hydrate(self, model) -> DTO- Build the DTO from a model. Override when the automatic conversion cannot
build your DTO at all; to merely add a derived field, prefer a
hydratehook. The override is the same thing as abuildhook, spelled as a method. See custom hydration.
@writes¶
@writes on a custom write method runs it through the repository's flush,
commit, and rollback, so the body only does session work. It works on both
Repository and ReadOnlyRepository, so a read-mostly repository can still own
an occasional hand-written write. The method may declare *, commit: bool | None
= None to expose the per-call override; without it the write flushes (and commits
if autocommit=True). See
transactions on custom writes.
Filter values¶
The values a **filters keyword understands beyond an ordinary match:
| Value | Effect |
|---|---|
None |
filter by IS NULL |
UNSET |
skip this filter entirely |
PaginatedResult¶
@dataclass(frozen=True, slots=True)
class PaginatedResult[DTO]:
items: list[DTO] # this page
total: int # all matching rows, ignoring offset/limit
Design principles¶
The ideas that explain every choice in the API.
- The session is the caller's. repositron flushes and never closes the
session, so transaction boundaries stay in your application code by default.
Committing is opt-in, per instance (
autocommit=True) or per write (commit=True); see transactions. A custom write gets the same deal through@writes, so you never hand-roll flush and rollback. - Extending is adding, not replacing. A hook layers
behavior onto what the base already does, so you write only the part that is
yours and inherit the rest. The base holds itself to the same rule: its own
DTO construction is a
buildhook you can replace, not a method you must reach around. An override is the rare fallback, not the extension point. - One source of truth per field name. A rename declared once in
field_mappingapplies to both hydration and projection. - Ordering is never implicit.
listandfirstare unordered unless asked;list_paginatedrefuses to run without one. See pagination. UNSETis one canonical singleton, compared by identity, shared across the whole library. There is no per-project variant to get subtly wrong.