Coverage for src/worblehat/queries/borrowing_queue.py: 24%
57 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
3from sqlalchemy import exists, select
4from sqlalchemy.orm import Session
6from worblehat.models import BookcaseItem, BorrowingLog, QueueEventType, QueueLog, QueuePosition
7from worblehat.queries.borrowing import has_active_borrowing
10def list_all_queue_items(sql_session: Session) -> list[QueuePosition]:
11 return list(
12 sql_session.scalars(
13 select(QueuePosition).order_by(
14 QueuePosition.entered_queue_time,
15 ),
16 ).all(),
17 )
20def get_queue_position(
21 sql_session: Session,
22 username: str,
23 item: BookcaseItem,
24) -> QueuePosition | None:
25 return sql_session.scalars(
26 select(QueuePosition).where(
27 QueuePosition.username == username,
28 QueuePosition.fk_bookcase_item_uid == item.uid,
29 ),
30 ).one_or_none()
33def is_in_borrowing_queue(sql_session: Session, username: str, item: BookcaseItem) -> bool:
34 return sql_session.scalar(
35 select(
36 exists().where(
37 QueuePosition.username == username,
38 QueuePosition.fk_bookcase_item_uid == item.uid,
39 ),
40 ),
41 )
44def list_queue_positions_for_item(
45 sql_session: Session,
46 item: BookcaseItem,
47) -> list[QueuePosition]:
48 return list(
49 sql_session.scalars(
50 select(QueuePosition)
51 .where(QueuePosition.fk_bookcase_item_uid == item.uid)
52 .order_by(QueuePosition.entered_queue_time),
53 ).all(),
54 )
57def list_pending_queue_items_for_item(
58 sql_session: Session,
59 item: BookcaseItem,
60) -> list[QueuePosition]:
61 return list(
62 sql_session.scalars(
63 select(QueuePosition)
64 .where(
65 QueuePosition.fk_bookcase_item_uid == item.uid,
66 QueuePosition.notified_available_time.is_(None),
67 )
68 .order_by(QueuePosition.entered_queue_time),
69 ).all(),
70 )
73def list_queue_log_for_item(sql_session: Session, item: BookcaseItem) -> list[QueueLog]:
74 return list(
75 sql_session.scalars(
76 select(QueueLog)
77 .where(QueueLog.fk_bookcase_item_uid == item.uid)
78 .order_by(QueueLog.uid),
79 ).all(),
80 )
83def join_borrowing_queue(
84 sql_session: Session,
85 username: str,
86 item: BookcaseItem,
87 _unsafe: bool = False,
88) -> QueuePosition:
89 if not _unsafe:
90 if has_active_borrowing(sql_session, username, item):
91 raise ValueError(f"{username} already has an active borrowing of this item")
92 if is_in_borrowing_queue(sql_session, username, item):
93 raise ValueError(f"{username} is already queued for this item")
95 sql_session.add(QueueLog(username, item, QueueEventType.JOINED))
96 sql_session.flush()
97 return sql_session.get_one(QueuePosition, (item.uid, username))
100def leave_borrowing_queue(
101 sql_session: Session,
102 position: QueuePosition,
103 _unsafe: bool = False,
104) -> None:
105 if not _unsafe and not is_in_borrowing_queue(sql_session, position.username, position.item):
106 raise ValueError(f"{position.username} is not currently queued for this item")
108 sql_session.add(QueueLog(position.username, position.item, QueueEventType.LEFT))
109 sql_session.flush()
110 sql_session.expunge(position)
113def claim_borrowing_queue_position(
114 sql_session: Session,
115 position: QueuePosition,
116 borrowing_log_entry: BorrowingLog,
117 _unsafe: bool = False,
118) -> None:
119 if not _unsafe:
120 if not is_in_borrowing_queue(sql_session, position.username, position.item):
121 raise ValueError(f"{position.username} is not currently queued for this item")
122 if (
123 borrowing_log_entry.username != position.username
124 or borrowing_log_entry.fk_bookcase_item_uid != position.fk_bookcase_item_uid
125 ):
126 raise ValueError("borrowing_log_entry does not belong to this queue position")
128 sql_session.add(
129 QueueLog(
130 position.username,
131 position.item,
132 QueueEventType.CLAIMED,
133 borrowing_log_entry=borrowing_log_entry,
134 ),
135 )
136 sql_session.flush()
137 sql_session.expunge(position)
140def expire_borrowing_queue_position(
141 sql_session: Session,
142 position: QueuePosition,
143 _unsafe: bool = False,
144) -> None:
145 if not _unsafe and not is_in_borrowing_queue(sql_session, position.username, position.item):
146 raise ValueError(f"{position.username} is not currently queued for this item")
148 sql_session.add(QueueLog(position.username, position.item, QueueEventType.EXPIRED))
149 sql_session.flush()
150 sql_session.expunge(position)
153def notify_borrowing_queue_position(
154 sql_session: Session,
155 position: QueuePosition,
156 notified_at: datetime | None = None,
157 _unsafe: bool = False,
158) -> QueuePosition:
159 if not _unsafe:
160 if not is_in_borrowing_queue(sql_session, position.username, position.item):
161 raise ValueError(f"{position.username} is not currently queued for this item")
162 if position.notified_available_time is not None:
163 raise ValueError(f"{position.username}'s queue position has already been notified")
165 sql_session.add(
166 QueueLog(
167 position.username,
168 position.item,
169 QueueEventType.NOTIFIED,
170 timestamp=notified_at,
171 ),
172 )
173 sql_session.flush()
174 sql_session.refresh(position)
175 return position