Skip to content

Commit d78d658

Browse files
committed
✨ [feat][backend] Enhenced read_events
1 parent 852f52f commit d78d658

3 files changed

Lines changed: 66 additions & 7 deletions

File tree

backend/kayman/crud/event.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from collections.abc import Sequence
1+
from collections.abc import Collection, Sequence
22
from datetime import date
33

4-
from sqlmodel import Session, func, select
4+
from sqlmodel import Session, col, func, select
55

66
from kayman.schemas.event import (
77
Event,
@@ -30,12 +30,26 @@ def read_event(session: Session, event_id: int) -> Event | None:
3030

3131

3232
def read_events(
33-
session: Session, event_date: date | None = None, category_id: int | None = None
33+
session: Session,
34+
event_ids: Collection[int] | None = None,
35+
event_date: date | None = None,
36+
category_id: int | None = None,
37+
for_update: bool = False,
3438
) -> Sequence[Event]:
35-
scalar = select(Event).distinct()
39+
scalar = select(Event)
40+
if event_ids:
41+
scalar = scalar.where(col(Event.id).in_(event_ids))
3642
if event_date:
3743
scalar = scalar.where(func.date(Event.timestamp) == event_date)
3844
if category_id:
39-
scalar = scalar.join(EventEntry).where(EventEntry.category_id == category_id)
45+
# distinct() only for the join, which can fan out an event into one row
46+
# per matching entry. Postgres rejects SELECT DISTINCT ... FOR UPDATE.
47+
scalar = (
48+
scalar.join(EventEntry)
49+
.where(EventEntry.category_id == category_id)
50+
.distinct()
51+
)
52+
if for_update:
53+
scalar = scalar.with_for_update()
4054
events = session.exec(scalar).all()
4155
return events

backend/kayman/routers/event.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def reads(
5353
event_date: date | None = None,
5454
category_id: int | None = None,
5555
) -> Sequence[EventBase]:
56-
return read_events(session, event_date, category_id)
56+
return read_events(session, event_date=event_date, category_id=category_id)
5757

5858

5959
@event_router.patch("", name="Update Event", response_model=EventRead)

backend/kayman/tests/crud/test_event.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from datetime import datetime
2+
from unittest.mock import patch
23

34
from sqlmodel import Session
45

@@ -13,8 +14,10 @@
1314
def test_create_events_1_event(session: Session, session_2: Session):
1415
event = EventFactory.build()
1516
db_event = create_events(session, [event])[0]
16-
db_read_event = read_event(session_2, db_event.id)
17+
db_read_events = read_events(session_2, event_ids=[db_event.id])
1718

19+
assert len(db_read_events) == 1
20+
db_read_event = db_read_events[0]
1821
assert db_read_event.id is not None
1922
assert db_read_event.description == event.description
2023
assert db_read_event.timestamp == event.timestamp
@@ -72,6 +75,20 @@ def test_read_event(session: Session):
7275
assert read_event(session, event.id) == event
7376

7477

78+
def test_read_events_by_ids(session: Session):
79+
event_1 = EventFactory()
80+
event_2 = EventFactory()
81+
EventFactory()
82+
83+
single = read_events(session, event_ids=[event_1.id])
84+
assert len(single) == 1
85+
assert single[0].id == event_1.id
86+
87+
multiple = read_events(session, event_ids=[event_1.id, event_2.id])
88+
assert len(multiple) == 2
89+
assert {event.id for event in multiple} == {event_1.id, event_2.id}
90+
91+
7592
def test_read_events_all(session: Session):
7693
for _ in range(10):
7794
EventFactory()
@@ -117,3 +134,31 @@ def test_read_events_by_category(session: Session):
117134
events = read_events(session, category_id=category_3.id)
118135
assert len(events) == 1
119136
assert events[0].id == event_2.id
137+
138+
139+
def test_read_events_by_category_deduplicates(session: Session):
140+
# Two entries of the same event in the same category. The join fans the
141+
# event out into one row per matching entry, so distinct() must collapse it
142+
# back to a single event.
143+
category = CategoryFactory()
144+
event = EventFactory(
145+
entries=[
146+
EventEntryFactory(category=category),
147+
EventEntryFactory(category=category),
148+
]
149+
)
150+
151+
events = read_events(session, category_id=category.id)
152+
153+
assert len(events) == 1
154+
assert events[0].id == event.id
155+
156+
157+
def test_read_events_for_update(session: Session):
158+
EventFactory()
159+
160+
with patch.object(session, "exec", wraps=session.exec) as mock_exec:
161+
read_events(session, for_update=True)
162+
args = mock_exec.call_args[0]
163+
statement = str(args[0])
164+
assert "FOR UPDATE" in statement

0 commit comments

Comments
 (0)