Get started¶
This page takes you from an empty file to a working, fully typed repository. Read it once, start to finish. After that, the guides cover each capability in depth.
Install¶
repositron runs on Python 3.13+ and SQLAlchemy 2.0.
SQLAlchemy is the only runtime dependency. If your return shapes are dataclasses, that is the whole footprint. Nothing else comes along for the ride.
A model to work with¶
Everything in these docs is built around one small domain: a task tracker, with
Task rows that belong to a workspace. It is an ordinary SQLAlchemy model, with
nothing repositron-specific in it, so if you already have models, point
repositron at those instead.
import datetime
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase): ...
class Task(Base):
__tablename__ = "tasks"
id: Mapped[int] = mapped_column(primary_key=True)
workspace_id: Mapped[int]
title: Mapped[str]
description: Mapped[str | None] = mapped_column(default=None)
status: Mapped[str] = mapped_column(default="open") # open | in_progress | done
assignee_id: Mapped[int | None] = mapped_column(default=None)
created_at: Mapped[datetime.datetime] = mapped_column(
default=lambda: datetime.datetime.now(datetime.UTC)
)
archived_at: Mapped[datetime.datetime | None] = mapped_column(default=None)
Declare the shapes¶
A repository is described entirely by its type parameters. There are four, and only the first is required:
Model is your SQLAlchemy class. DTO is what reads hand back. Create and
Update are the dataclasses your writes accept. We will use all four so the
full picture is on the table, then come back later and learn how to drop the
ones you do not need.
from dataclasses import dataclass
from repositron import UNSET, UnsetType
@dataclass(frozen=True, slots=True)
class TaskDTO: # the shape reads return
id: int
title: str
status: str
assignee_id: int | None
@dataclass
class TaskCreate: # what create() accepts
workspace_id: int
title: str
description: str | None | UnsetType = UNSET
assignee_id: int | None | UnsetType = UNSET
@dataclass
class TaskUpdate: # what update() accepts
title: str | UnsetType = UNSET
status: str | UnsetType = UNSET
assignee_id: int | None | UnsetType = UNSET # None = unassign (SET NULL)
TaskDTO is deliberately narrower than the model, it carries what a list view
needs, not every column. The UNSET defaults on the payloads are how a field
gets left alone on a write; updating rows covers why that
matters.
Wire the three together by subclassing Repository:
from repositron import Repository
class TaskRepository(Repository[Task, TaskDTO, TaskCreate, TaskUpdate]):
pass
That is the entire class. Everything below uses this TaskRepository. The
guides add the knobs, field_mapping for renamed columns, a
non-int key, hooks, as you need them.
Use it¶
Hand the repository a session and every method is already there, already typed:
repo = TaskRepository(session)
repo.get(1) # TaskDTO | None
repo.list(workspace_id=42, status="open") # list[TaskDTO]
repo.count(workspace_id=42) # int
repo.exists(1) # bool
repo.create(TaskCreate(workspace_id=42, title="Ship the docs")) # int (new id)
repo.update(1, TaskUpdate(status="done")) # True; title untouched
repo.delete(1) # bool
Hover any of those calls in your editor. repo.list() is list[TaskDTO], not
list[Any]. The same object you return here is the object your web framework
serializes, so there is no second schema to keep in step.
The session stays yours
repositron never opens or closes the session, and by default writes only
flush, leaving when to commit and roll back to your application. When you
do want a write committed, opt in with Repository(session, autocommit=True)
or per call with repo.create(payload, commit=True); see
transactions. One repository instance holds
no per-call state, so it is safe to build once and inject everywhere. This
boundary is one of the design principles.
Where to go next¶
You now have a working repository. From here, the Guides take each capability one at a time, filtering, updating, pagination, projection, return types, hooks, and the escape hatch for custom queries.