Coverage for src/worblehat/queries/deadline_daemon.py: 94%
28 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-25 22:25 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-09-25 22:25 +0000
1from datetime import datetime, timedelta
3from sqlalchemy import func, select
4from sqlalchemy.orm import Session
5from sqlalchemy.sql.elements import SQLColumnExpression
7from worblehat.models import (
8 Borrowing,
9 BorrowingEventType,
10 BorrowingLog,
11 DeadlineDaemonLastRunDatetime,
12 QueuePosition,
13)
16def _sql_subtract_date(
17 sql_session: Session,
18 x: SQLColumnExpression[datetime],
19 y: timedelta,
20) -> SQLColumnExpression[datetime]:
21 dialect_name = sql_session.get_bind().dialect.name
22 if dialect_name == "sqlite":
23 # SQLite does not support timedelta in queries
24 return func.datetime(x, f"-{y.days} days")
25 if dialect_name == "postgresql": 25 ↛ 27line 25 didn't jump to line 27 because the condition on line 25 was always true
26 return x - y
27 raise NotImplementedError(
28 f"Unsupported dialect: {dialect_name}",
29 )
32def find_last_run(sql_session: Session) -> DeadlineDaemonLastRunDatetime | None:
33 return sql_session.scalars(
34 select(DeadlineDaemonLastRunDatetime),
35 ).one_or_none()
38def list_close_deadline_borrowings(
39 sql_session: Session,
40 day: int,
41 last_run_datetime: datetime,
42 current_run_datetime: datetime,
43) -> list[Borrowing]:
44 return list(
45 sql_session.scalars(
46 select(Borrowing).where(
47 _sql_subtract_date(
48 sql_session,
49 Borrowing.due_time,
50 timedelta(days=day),
51 ).between(
52 last_run_datetime,
53 current_run_datetime,
54 ),
55 ),
56 ).all(),
57 )
60def list_undelivered_overdue_borrowings(
61 sql_session: Session,
62 current_run_datetime: datetime,
63) -> list[Borrowing]:
64 return list(
65 sql_session.scalars(
66 select(Borrowing).where(
67 Borrowing.due_time < current_run_datetime,
68 ),
69 ).all(),
70 )
73def list_newly_available_queue_items(
74 sql_session: Session,
75 last_run_datetime: datetime,
76 current_run_datetime: datetime,
77) -> list[QueuePosition]:
78 items_returned_since_last_run = (
79 select(BorrowingLog.fk_bookcase_item_uid)
80 .where(
81 BorrowingLog.event_type == BorrowingEventType.RETURNED,
82 BorrowingLog.timestamp.between(last_run_datetime, current_run_datetime),
83 )
84 .distinct()
85 )
87 return list(
88 sql_session.scalars(
89 select(QueuePosition)
90 .where(
91 QueuePosition.notified_available_time.is_(None),
92 QueuePosition.fk_bookcase_item_uid.in_(items_returned_since_last_run),
93 )
94 .order_by(QueuePosition.entered_queue_time)
95 .group_by(QueuePosition.fk_bookcase_item_uid),
96 ).all(),
97 )
100def list_expiring_queue_positions(
101 sql_session: Session,
102 last_run_datetime: datetime,
103 current_run_datetime: datetime,
104) -> list[QueuePosition]:
105 return list(
106 sql_session.scalars(
107 select(QueuePosition).where(
108 QueuePosition.notified_available_time.between(
109 last_run_datetime,
110 current_run_datetime,
111 ),
112 ),
113 ).all(),
114 )
117def list_overdue_queue_positions(
118 sql_session: Session,
119 queue_position_expiry_days: int,
120 current_run_datetime: datetime,
121) -> list[QueuePosition]:
122 expiry_cutoff = current_run_datetime - timedelta(days=queue_position_expiry_days)
123 return list(
124 sql_session.scalars(
125 select(QueuePosition).where(
126 QueuePosition.notified_available_time < expiry_cutoff,
127 ),
128 ).all(),
129 )
132def find_next_queue_position(
133 sql_session: Session,
134 item_uid: int,
135) -> QueuePosition | None:
136 return sql_session.scalars(
137 select(QueuePosition)
138 .where(
139 QueuePosition.fk_bookcase_item_uid == item_uid,
140 QueuePosition.notified_available_time.is_(None),
141 )
142 .order_by(QueuePosition.entered_queue_time)
143 .limit(1),
144 ).one_or_none()