Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions backend/druks/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,24 @@ async def get_or_none(cls, **fields: object) -> Self | None:

return (await db_session().scalars(cls._select_matching(fields))).one_or_none()

@classmethod
async def all(cls) -> list[Self]:
"""Every row, in the class's ordering, or in primary key order when it
declares none."""
return await cls._list_matching({})

@classmethod
async def filter(cls, **fields: object) -> list[Self]:
"""The rows whose fields hold these values, in the class's ordering, or in
primary key order when it declares none."""
"""The rows whose fields hold these values, ordered like ``all()``."""
if fields:
return await cls._list_matching(fields)
raise TypeError(
f"{cls.__name__}.filter() needs a field to match. "
f"Call {cls.__name__}.all() to read every row."
)

@classmethod
async def _list_matching(cls, fields: dict[str, object]) -> list[Self]:
from druks.db import db_session

ordering = cls._get_order_by() or cls.__mapper__.primary_key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ from druks.db import Model, StoredSubject
# SQLAlchemy models for this app — subclass ``Model``. Druks names each table for the
# app and the class ("{{ name }}_report" for ``Report``), keeps the app's own migration
# history in ``alembic_version_{{ name }}``, and enforces the "{{ name }}_" prefix at boot.
# A model reads by field (``get``, ``get_or_none``, ``filter``) and writes with
# A model reads with ``all``, ``filter``, ``get``, and ``get_or_none`` and writes with
# ``create``, ``save``, and ``delete``; ``class Report(Model, ordering=("-created_at",))``
# sets the order ``filter`` reads in.
# sets the order ``all`` and ``filter`` read in.
# One of these models is usually the thing your runs work on — a note, a repo, a
# ticket. Subclass ``StoredSubject`` for that one and point a workflow at it with
# ``subject = ThatModel``: it also gets an id, created_at, updated_at, a board, and a
Expand Down
3 changes: 3 additions & 0 deletions backend/tests/druks-field_notes/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ async def test_rows_read_by_field_in_the_declared_order(druks_db):
first = await Note.create(body="the pump ran hot")
second = await Note.create(body="the pump ran hot")

assert await Note.all() == [second, first]
assert await Note.filter(body="the pump ran hot") == [second, first]
with pytest.raises(TypeError, match="Note.all()"):
await Note.filter()
assert await Note.get(id=str(second.id)) == second
assert await Note.get_or_none(id="not an id") is None
with pytest.raises(ObjectNotFound, match="No note with id 0"):
Expand Down
6 changes: 4 additions & 2 deletions docs/writing-an-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,7 @@ A model reads and writes by field:
report = await Report.create(repo="acme/widgets", status="open")
report = await Report.get(id=report_id)
report = await Report.get_or_none(repo="acme/widgets")
reports = await Report.all()
open_reports = await Report.filter(status="open")
report.status = "closed"
await report.save()
Expand All @@ -1094,8 +1095,9 @@ await report.delete()
page with an empty state, so a route or page that names a row by id never
spells either. `get_or_none` answers None instead. Both expect one row: two
raise SQLAlchemy's `MultipleResultsFound`, so back the fields they read with a
unique constraint. `filter` returns the matching rows in primary key order, or in
the order the class declares on its class line, in Django's form:
unique constraint. `all` returns every row and `filter` the rows that match at
least one field, both in primary key order, or in the order the class declares
on its class line, in Django's form:

```python
class Report(Model, ordering=("-created_at",)):
Expand Down
Loading