libres 0.7.3__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
libres/db/scheduler.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import sedate
2
4
 
3
5
  from datetime import datetime, time, timedelta
@@ -16,8 +18,14 @@ from libres.modules import rasterizer
16
18
  from libres.modules import utils
17
19
 
18
20
 
19
- import typing as _t
20
- if _t.TYPE_CHECKING:
21
+ from typing import overload
22
+ from typing import Any
23
+ from typing import Literal
24
+ from typing import TYPE_CHECKING
25
+ if TYPE_CHECKING:
26
+ from collections.abc import Collection
27
+ from collections.abc import Iterable
28
+ from collections.abc import Iterator
21
29
  from sedate.types import DateLike
22
30
  from sqlalchemy.orm import Query
23
31
  from typing_extensions import NotRequired, Self, TypeAlias, TypedDict
@@ -25,32 +33,32 @@ if _t.TYPE_CHECKING:
25
33
  from libres.context.core import Context
26
34
  from libres.modules.rasterizer import Raster
27
35
 
28
- _dtrange: TypeAlias = _t.Tuple[datetime, datetime]
36
+ _dtrange: TypeAlias = tuple[datetime, datetime] # noqa: PYI042
29
37
 
30
38
  class _ReserveArgs1(TypedDict):
31
39
  email: str
32
- dates: _t.Union['_dtrange', _t.Collection['_dtrange']]
33
- data: NotRequired[_t.Optional[_t.Any]]
34
- session_id: NotRequired[_t.Optional[UUID]]
40
+ dates: _dtrange | Collection[_dtrange]
41
+ data: NotRequired[Any | None]
42
+ session_id: NotRequired[UUID | None]
35
43
  quota: NotRequired[int]
36
44
  single_token_per_session: NotRequired[bool]
37
45
 
38
46
  class _ReserveArgs2(TypedDict):
39
47
  email: str
40
48
  group: UUID
41
- data: NotRequired[_t.Optional[_t.Any]]
42
- session_id: NotRequired[_t.Optional[UUID]]
49
+ data: NotRequired[Any | None]
50
+ session_id: NotRequired[UUID | None]
43
51
  quota: NotRequired[int]
44
52
  single_token_per_session: NotRequired[bool]
45
53
 
46
- _ReserveArgs: TypeAlias = _t.Union[_ReserveArgs1, _ReserveArgs2]
54
+ _ReserveArgs: TypeAlias = '_ReserveArgs1 | _ReserveArgs2'
47
55
 
48
56
 
49
57
  missing = object()
50
58
 
51
- Day = _t.Literal['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
52
- DayNumber = _t.Literal[0, 1, 2, 3, 4, 5, 6]
53
- DAYS_MAP: _t.Dict[Day, DayNumber] = {
59
+ Day: TypeAlias = Literal['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
60
+ DayNumber: TypeAlias = Literal[0, 1, 2, 3, 4, 5, 6]
61
+ DAYS_MAP: dict[Day, DayNumber] = {
54
62
  'mo': 0,
55
63
  'tu': 1,
56
64
  'we': 2,
@@ -68,12 +76,12 @@ class Scheduler(ContextServicesMixin):
68
76
 
69
77
  def __init__(
70
78
  self,
71
- context: 'Context',
79
+ context: Context,
72
80
  name: str,
73
81
  # FIXME: Not quite sure why we don't allow a PyTzInfo
74
82
  timezone: str,
75
- allocation_cls: _t.Type[Allocation] = Allocation,
76
- reservation_cls: _t.Type[Reservation] = Reservation
83
+ allocation_cls: type[Allocation] = Allocation,
84
+ reservation_cls: type[Reservation] = Reservation
77
85
  ):
78
86
  """ Initializeds a new Scheduler instance.
79
87
 
@@ -112,7 +120,7 @@ class Scheduler(ContextServicesMixin):
112
120
  self.allocation_cls = allocation_cls
113
121
  self.reservation_cls = reservation_cls
114
122
 
115
- def clone(self) -> 'Self':
123
+ def clone(self) -> Self:
116
124
  """ Clones the scheduler. The result will be a new scheduler using the
117
125
  same context, name, settings and attributes.
118
126
 
@@ -145,8 +153,8 @@ class Scheduler(ContextServicesMixin):
145
153
 
146
154
  def _prepare_dates(
147
155
  self,
148
- dates: 'utils._NestedIterable[datetime]'
149
- ) -> _t.List[_t.Tuple[datetime, datetime]]:
156
+ dates: utils._NestedIterable[datetime]
157
+ ) -> list[tuple[datetime, datetime]]:
150
158
  return [
151
159
  (
152
160
  sedate.standardize_date(s, self.timezone),
@@ -158,20 +166,20 @@ class Scheduler(ContextServicesMixin):
158
166
  self,
159
167
  start: datetime,
160
168
  end: datetime
161
- ) -> _t.Tuple[datetime, datetime]:
169
+ ) -> tuple[datetime, datetime]:
162
170
  return (
163
171
  sedate.standardize_date(start, self.timezone),
164
172
  sedate.standardize_date(end, self.timezone)
165
173
  )
166
174
 
167
- def managed_allocations(self) -> 'Query[Allocation]':
175
+ def managed_allocations(self) -> Query[Allocation]:
168
176
  """ The allocations managed by this scheduler / resource. """
169
177
  query = self.session.query(Allocation)
170
178
  query = query.filter(Allocation.mirror_of == self.resource)
171
179
 
172
180
  return query
173
181
 
174
- def managed_reserved_slots(self) -> 'Query[ReservedSlot]':
182
+ def managed_reserved_slots(self) -> Query[ReservedSlot]:
175
183
  """ The reserved_slots managed by this scheduler / resource. """
176
184
  uuids = self.managed_allocations().with_entities(Allocation.resource)
177
185
 
@@ -180,7 +188,7 @@ class Scheduler(ContextServicesMixin):
180
188
 
181
189
  return query
182
190
 
183
- def managed_reservations(self) -> 'Query[Reservation]':
191
+ def managed_reservations(self) -> Query[Reservation]:
184
192
  """ The reservations managed by this scheduler / resource. """
185
193
  query = self.session.query(Reservation)
186
194
  query = query.filter(Reservation.resource == self.resource)
@@ -205,8 +213,8 @@ class Scheduler(ContextServicesMixin):
205
213
 
206
214
  def allocations_by_ids(
207
215
  self,
208
- ids: _t.Collection[int]
209
- ) -> 'Query[Allocation]':
216
+ ids: Collection[int]
217
+ ) -> Query[Allocation]:
210
218
  query = self.managed_allocations()
211
219
  query = query.filter(Allocation.id.in_(ids))
212
220
  query = query.order_by(Allocation._start)
@@ -216,14 +224,14 @@ class Scheduler(ContextServicesMixin):
216
224
  self,
217
225
  group: UUID,
218
226
  masters_only: bool = True
219
- ) -> 'Query[Allocation]':
227
+ ) -> Query[Allocation]:
220
228
  return self.allocations_by_groups([group], masters_only=masters_only)
221
229
 
222
230
  def allocations_by_groups(
223
231
  self,
224
- groups: _t.Collection[UUID],
232
+ groups: Collection[UUID],
225
233
  masters_only: bool = True
226
- ) -> 'Query[Allocation]':
234
+ ) -> Query[Allocation]:
227
235
 
228
236
  query = self.managed_allocations()
229
237
  query = query.filter(Allocation.group.in_(groups))
@@ -236,8 +244,8 @@ class Scheduler(ContextServicesMixin):
236
244
  def allocations_by_reservation(
237
245
  self,
238
246
  token: UUID,
239
- id: _t.Optional[int] = None
240
- ) -> 'Query[Allocation]':
247
+ id: int | None = None
248
+ ) -> Query[Allocation]:
241
249
  """ Returns the allocations for the reservation if it was *approved*,
242
250
  pending reservations return nothing. If you need to get the allocation
243
251
  a pending reservation might be targeting, use _target_allocations
@@ -276,7 +284,7 @@ class Scheduler(ContextServicesMixin):
276
284
  start: datetime,
277
285
  end: datetime,
278
286
  masters_only: bool = True
279
- ) -> 'Query[Allocation]':
287
+ ) -> Query[Allocation]:
280
288
 
281
289
  start, end = self._prepare_range(start, end)
282
290
 
@@ -295,24 +303,27 @@ class Scheduler(ContextServicesMixin):
295
303
  def allocation_dates_by_group(
296
304
  self,
297
305
  group: UUID
298
- ) -> _t.List[_t.Tuple[datetime, datetime]]:
306
+ ) -> list[tuple[datetime, datetime]]:
299
307
 
300
308
  query = self.allocations_by_group(group)
301
- dates_query = query.with_entities(Allocation._start, Allocation._end)
309
+ dates_query: Query[tuple[datetime, datetime]] = query.with_entities(
310
+ Allocation._start,
311
+ Allocation._end
312
+ )
302
313
  return dates_query.all()
303
314
 
304
315
  def allocation_mirrors_by_master(
305
316
  self,
306
317
  master: Allocation
307
- ) -> _t.List[Allocation]:
318
+ ) -> list[Allocation]:
308
319
  return [s for s in master.siblings() if not s.is_master]
309
320
 
310
321
  def allocation_dates_by_ids(
311
322
  self,
312
- ids: _t.Collection[int],
313
- start_time: _t.Optional[time] = None,
314
- end_time: _t.Optional[time] = None
315
- ) -> _t.Iterator[_t.Tuple[datetime, datetime]]:
323
+ ids: Collection[int],
324
+ start_time: time | None = None,
325
+ end_time: time | None = None
326
+ ) -> Iterator[tuple[datetime, datetime]]:
316
327
 
317
328
  for allocation in self.allocations_by_ids(ids).all():
318
329
 
@@ -323,26 +334,26 @@ class Scheduler(ContextServicesMixin):
323
334
 
324
335
  yield s_dt, e_dt - timedelta(microseconds=1)
325
336
 
326
- def manual_approval_required(self, ids: _t.Collection[int]) -> bool:
337
+ def manual_approval_required(self, ids: Collection[int]) -> bool:
327
338
  """ Returns True if any of the allocations require manual approval. """
328
339
  query = self.allocations_by_ids(ids)
329
340
  query = query.filter(Allocation.approve_manually == True)
330
341
 
331
- return self.session.query(query.exists()).scalar()
342
+ return self.session.query(query.exists()).scalar() # type: ignore[no-any-return]
332
343
 
333
344
  def allocate(
334
345
  self,
335
- dates: _t.Union['_dtrange', _t.Iterable['_dtrange']],
346
+ dates: _dtrange | Iterable[_dtrange],
336
347
  partly_available: bool = False,
337
- raster: 'Raster' = rasterizer.MIN_RASTER,
348
+ raster: Raster = rasterizer.MIN_RASTER,
338
349
  whole_day: bool = False,
339
- quota: _t.Optional[int] = None,
350
+ quota: int | None = None,
340
351
  quota_limit: int = 0,
341
352
  grouped: bool = False,
342
- data: _t.Optional[_t.Any] = None,
353
+ data: Any | None = None,
343
354
  approve_manually: bool = False,
344
355
  skip_overlapping: bool = False,
345
- ) -> _t.List[Allocation]:
356
+ ) -> list[Allocation]:
346
357
  """ Allocates a spot in the sedate.
347
358
 
348
359
  An allocation defines a timerange which can be reserved. No
@@ -596,7 +607,7 @@ class Scheduler(ContextServicesMixin):
596
607
 
597
608
  """
598
609
 
599
- assert new_quota > 0, "Quota must be greater than 0"
610
+ assert new_quota > 0, 'Quota must be greater than 0'
600
611
 
601
612
  if new_quota == master.quota:
602
613
  return
@@ -607,7 +618,7 @@ class Scheduler(ContextServicesMixin):
607
618
 
608
619
  # Make sure that the quota can be decreased
609
620
  mirrors = self.allocation_mirrors_by_master(master)
610
- allocations = [master] + mirrors
621
+ allocations = [master, *mirrors]
611
622
 
612
623
  free_allocations = [a for a in allocations if a.is_available()]
613
624
 
@@ -666,9 +677,9 @@ class Scheduler(ContextServicesMixin):
666
677
 
667
678
  def reordered_keylist(
668
679
  self,
669
- allocations: _t.Collection[Allocation],
680
+ allocations: Collection[Allocation],
670
681
  new_quota: int
671
- ) -> _t.Dict[UUID, _t.Optional[UUID]]:
682
+ ) -> dict[UUID, UUID | None]:
672
683
  """ Creates the map for the keylist reorganzation.
673
684
 
674
685
  Each key of the returned dictionary is a resource uuid pointing to the
@@ -687,7 +698,7 @@ class Scheduler(ContextServicesMixin):
687
698
  keylist.extend(utils.generate_uuids(master.resource, master.quota))
688
699
 
689
700
  # prefill the map
690
- reordered: _t.Dict[UUID, _t.Optional[UUID]] = {
701
+ reordered: dict[UUID, UUID | None] = {
691
702
  k: None
692
703
  for k in keylist
693
704
  }
@@ -705,8 +716,8 @@ class Scheduler(ContextServicesMixin):
705
716
 
706
717
  def availability(
707
718
  self,
708
- start: _t.Optional[datetime] = None,
709
- end: _t.Optional[datetime] = None
719
+ start: datetime | None = None,
720
+ end: datetime | None = None
710
721
  ) -> float:
711
722
  """Goes through all allocations and sums up the availability."""
712
723
 
@@ -720,14 +731,14 @@ class Scheduler(ContextServicesMixin):
720
731
  def move_allocation(
721
732
  self,
722
733
  master_id: int,
723
- new_start: _t.Optional[datetime] = None,
724
- new_end: _t.Optional[datetime] = None,
725
- group: _t.Optional[UUID] = None,
726
- new_quota: _t.Optional[int] = None,
727
- approve_manually: _t.Optional[bool] = None,
734
+ new_start: datetime | None = None,
735
+ new_end: datetime | None = None,
736
+ group: UUID | None = None,
737
+ new_quota: int | None = None,
738
+ approve_manually: bool | None = None,
728
739
  quota_limit: int = 0,
729
- whole_day: _t.Optional[bool] = None,
730
- data: _t.Optional[_t.Any] = missing
740
+ whole_day: bool | None = None,
741
+ data: Any | None = missing
731
742
  ) -> None:
732
743
 
733
744
  assert master_id
@@ -740,7 +751,7 @@ class Scheduler(ContextServicesMixin):
740
751
  master = self.allocation_by_id(master_id)
741
752
  mirrors = self.allocation_mirrors_by_master(master)
742
753
 
743
- changing = [master] + mirrors
754
+ changing = [master, *mirrors]
744
755
  ids = [c.id for c in changing]
745
756
 
746
757
  assert master.timezone == self.timezone, """
@@ -834,24 +845,24 @@ class Scheduler(ContextServicesMixin):
834
845
  if data is not missing:
835
846
  change.data = data
836
847
 
837
- @_t.overload
848
+ @overload
838
849
  def remove_allocation(self, id: int) -> None: ...
839
850
 
840
- @_t.overload
851
+ @overload
841
852
  def remove_allocation(
842
853
  self,
843
854
  id: None = ...,
844
855
  *,
845
- groups: _t.Collection[UUID]
856
+ groups: Collection[UUID]
846
857
  ) -> None: ...
847
858
 
848
859
  def remove_allocation(
849
860
  self,
850
- id: _t.Optional[int] = None,
851
- groups: _t.Optional[_t.Collection[UUID]] = None
861
+ id: int | None = None,
862
+ groups: Collection[UUID] | None = None
852
863
  ) -> None:
853
864
 
854
- allocations: _t.Iterable[Allocation]
865
+ allocations: Iterable[Allocation]
855
866
  if id:
856
867
  # FIXME: We probably should `assert groups is None`
857
868
  # since the parameter does nothing if `id` is
@@ -894,9 +905,9 @@ class Scheduler(ContextServicesMixin):
894
905
 
895
906
  def remove_unused_allocations(
896
907
  self,
897
- start: 'DateLike',
898
- end: 'DateLike',
899
- days: _t.Optional[_t.Iterable[_t.Union['Day', 'DayNumber']]] = None,
908
+ start: DateLike,
909
+ end: DateLike,
910
+ days: Iterable[Day | DayNumber] | None = None,
900
911
  exclude_groups: bool = False
901
912
  ) -> int:
902
913
  """ Removes all allocations without reservations between start and
@@ -922,7 +933,7 @@ class Scheduler(ContextServicesMixin):
922
933
  sedate.as_datetime(end)
923
934
  )
924
935
 
925
- day_numbers: _t.Optional[_t.Set[DayNumber]] = None
936
+ day_numbers: set[DayNumber] | None = None
926
937
  if days:
927
938
  # get the day from the map - if impossible take the verbatim value
928
939
  # this allows for using strings or integers
@@ -1001,39 +1012,39 @@ class Scheduler(ContextServicesMixin):
1001
1012
  deleted += 1
1002
1013
  return deleted
1003
1014
 
1004
- @_t.overload
1015
+ @overload
1005
1016
  def reserve(
1006
1017
  self,
1007
1018
  email: str,
1008
- dates: _t.Union['_dtrange', _t.Collection['_dtrange']],
1019
+ dates: _dtrange | Collection[_dtrange],
1009
1020
  group: None = ...,
1010
- data: _t.Optional[_t.Any] = ...,
1011
- session_id: _t.Optional[UUID] = ...,
1021
+ data: Any | None = ...,
1022
+ session_id: UUID | None = ...,
1012
1023
  quota: int = ...,
1013
1024
  single_token_per_session: bool = ...
1014
1025
  ) -> UUID: ...
1015
1026
 
1016
- @_t.overload
1027
+ @overload
1017
1028
  def reserve(
1018
1029
  self,
1019
1030
  email: str,
1020
1031
  dates: None,
1021
1032
  group: UUID,
1022
- data: _t.Optional[_t.Any] = ...,
1023
- session_id: _t.Optional[UUID] = ...,
1033
+ data: Any | None = ...,
1034
+ session_id: UUID | None = ...,
1024
1035
  quota: int = ...,
1025
1036
  single_token_per_session: bool = ...
1026
1037
  ) -> UUID: ...
1027
1038
 
1028
- @_t.overload
1039
+ @overload
1029
1040
  def reserve(
1030
1041
  self,
1031
1042
  email: str,
1032
1043
  dates: None = ...,
1033
1044
  *,
1034
1045
  group: UUID,
1035
- data: _t.Optional[_t.Any] = ...,
1036
- session_id: _t.Optional[UUID] = ...,
1046
+ data: Any | None = ...,
1047
+ session_id: UUID | None = ...,
1037
1048
  quota: int = ...,
1038
1049
  single_token_per_session: bool = ...
1039
1050
  ) -> UUID: ...
@@ -1041,10 +1052,10 @@ class Scheduler(ContextServicesMixin):
1041
1052
  def reserve(
1042
1053
  self,
1043
1054
  email: str,
1044
- dates: _t.Union['_dtrange', _t.Collection['_dtrange'], None] = None,
1045
- group: _t.Optional[UUID] = None,
1046
- data: _t.Optional[_t.Any] = None,
1047
- session_id: _t.Optional[UUID] = None,
1055
+ dates: _dtrange | Collection[_dtrange] | None = None,
1056
+ group: UUID | None = None,
1057
+ data: Any | None = None,
1058
+ session_id: UUID | None = None,
1048
1059
  quota: int = 1,
1049
1060
  single_token_per_session: bool = False
1050
1061
  ) -> UUID:
@@ -1173,9 +1184,8 @@ class Scheduler(ContextServicesMixin):
1173
1184
  if not allocation.contains(start, end):
1174
1185
  raise errors.TimerangeTooLong()
1175
1186
 
1176
- if allocation.quota_limit > 0:
1177
- if allocation.quota_limit < quota:
1178
- raise errors.QuotaOverLimit
1187
+ if 0 < allocation.quota_limit < quota:
1188
+ raise errors.QuotaOverLimit
1179
1189
 
1180
1190
  if allocation.quota < quota:
1181
1191
  raise errors.QuotaImpossible
@@ -1194,8 +1204,8 @@ class Scheduler(ContextServicesMixin):
1194
1204
  # or none of them. As such there's no start / end date which is defined
1195
1205
  # implicitly by the allocation
1196
1206
  def new_reservations_by_group(
1197
- group: _t.Optional[UUID]
1198
- ) -> _t.Iterator[Reservation]:
1207
+ group: UUID | None
1208
+ ) -> Iterator[Reservation]:
1199
1209
 
1200
1210
  if group:
1201
1211
  reservation = self.reservation_cls()
@@ -1213,8 +1223,8 @@ class Scheduler(ContextServicesMixin):
1213
1223
 
1214
1224
  # all other reservations are reserved by start/end date
1215
1225
  def new_reservations_by_dates(
1216
- dates: _t.List[_t.Tuple[datetime, datetime]]
1217
- ) -> _t.Iterator[Reservation]:
1226
+ dates: list[tuple[datetime, datetime]]
1227
+ ) -> Iterator[Reservation]:
1218
1228
 
1219
1229
  already_reserved_groups = set()
1220
1230
 
@@ -1266,8 +1276,9 @@ class Scheduler(ContextServicesMixin):
1266
1276
  # reservation twice on a single session
1267
1277
  if session_id:
1268
1278
  found = self.queries.reservations_by_session(session_id)
1269
- found = found.with_entities(Reservation.target, Reservation.start)
1270
- found_set = set(found)
1279
+ found_set: set[tuple[UUID, datetime | None]] = set(
1280
+ found.with_entities(Reservation.target, Reservation.start)
1281
+ )
1271
1282
 
1272
1283
  for reservation in reservations:
1273
1284
  if (reservation.target, reservation.start) in found_set:
@@ -1283,12 +1294,12 @@ class Scheduler(ContextServicesMixin):
1283
1294
  def _approve_reservation_record(
1284
1295
  self,
1285
1296
  reservation: Reservation
1286
- ) -> _t.List[ReservedSlot]:
1297
+ ) -> list[ReservedSlot]:
1287
1298
 
1288
1299
  # write out the slots
1289
1300
  slots_to_reserve = []
1290
1301
 
1291
- dates: _t.Union[_t.Tuple['_dtrange', ...], _t.List['_dtrange']]
1302
+ dates: tuple[_dtrange, ...] | list[_dtrange]
1292
1303
  if reservation.target_type == 'group':
1293
1304
  dates = self.allocation_dates_by_group(reservation.target)
1294
1305
  else:
@@ -1330,7 +1341,7 @@ class Scheduler(ContextServicesMixin):
1330
1341
 
1331
1342
  return slots_to_reserve
1332
1343
 
1333
- def approve_reservations(self, token: UUID) -> _t.List[ReservedSlot]:
1344
+ def approve_reservations(self, token: UUID) -> list[ReservedSlot]:
1334
1345
  """ This function approves an existing reservation and writes the
1335
1346
  reserved slots accordingly.
1336
1347
 
@@ -1342,14 +1353,14 @@ class Scheduler(ContextServicesMixin):
1342
1353
 
1343
1354
  reservations = self.reservations_by_token(token).all()
1344
1355
 
1345
- for reservation in reservations:
1346
- try:
1356
+ try:
1357
+ for reservation in reservations:
1347
1358
  slots_to_reserve.extend(
1348
1359
  self._approve_reservation_record(reservation)
1349
1360
  )
1350
- except errors.LibresError as e:
1351
- e.reservation = reservation
1352
- raise e
1361
+ except errors.LibresError as e:
1362
+ e.reservation = reservation
1363
+ raise e
1353
1364
 
1354
1365
  events.on_reservations_approved(self.context, reservations)
1355
1366
 
@@ -1373,7 +1384,7 @@ class Scheduler(ContextServicesMixin):
1373
1384
  def remove_reservation(
1374
1385
  self,
1375
1386
  token: UUID,
1376
- id: _t.Optional[int] = None
1387
+ id: int | None = None
1377
1388
  ) -> None:
1378
1389
  """ Removes all reserved slots of the given reservation token.
1379
1390
 
@@ -1411,7 +1422,7 @@ class Scheduler(ContextServicesMixin):
1411
1422
  def change_reservation_data(
1412
1423
  self,
1413
1424
  token: UUID,
1414
- data: _t.Optional[_t.Any]
1425
+ data: Any | None
1415
1426
  ) -> None:
1416
1427
 
1417
1428
  for reservation in self.reservations_by_token(token).all():
@@ -1419,8 +1430,8 @@ class Scheduler(ContextServicesMixin):
1419
1430
 
1420
1431
  def change_reservation_time_candidates(
1421
1432
  self,
1422
- tokens: _t.Optional[_t.Collection[UUID]] = None
1423
- ) -> 'Query[Reservation]':
1433
+ tokens: Collection[UUID] | None = None
1434
+ ) -> Query[Reservation]:
1424
1435
  """ Returns the reservations that fullfill the restrictions
1425
1436
  imposed by change_reservation_time.
1426
1437
 
@@ -1448,7 +1459,7 @@ class Scheduler(ContextServicesMixin):
1448
1459
  id: int,
1449
1460
  new_start: datetime,
1450
1461
  new_end: datetime
1451
- ) -> _t.Optional[Reservation]:
1462
+ ) -> Reservation | None:
1452
1463
  """ Kept for backwards compatibility, use :meth:`change_reservation`
1453
1464
  instead.
1454
1465
 
@@ -1468,8 +1479,8 @@ class Scheduler(ContextServicesMixin):
1468
1479
  id: int,
1469
1480
  new_start: datetime,
1470
1481
  new_end: datetime,
1471
- quota: _t.Optional[int] = None
1472
- ) -> _t.Optional[Reservation]:
1482
+ quota: int | None = None
1483
+ ) -> Reservation | None:
1473
1484
  """ Allows to change the timespan of a reservation under certain
1474
1485
  conditions:
1475
1486
 
@@ -1493,7 +1504,7 @@ class Scheduler(ContextServicesMixin):
1493
1504
  existing_reservation = self.reservations_by_token(token, id).one()
1494
1505
 
1495
1506
  # if there's nothing to change, do not change
1496
- if quota is None or existing_reservation.quota == quota:
1507
+ if quota is None or existing_reservation.quota == quota: # noqa: SIM102
1497
1508
  if existing_reservation.start == new_start:
1498
1509
  ends = (new_end, new_end - timedelta(microseconds=1))
1499
1510
 
@@ -1511,7 +1522,7 @@ class Scheduler(ContextServicesMixin):
1511
1522
  if not allocation.contains(new_start, new_end):
1512
1523
  raise errors.TimerangeTooLong()
1513
1524
 
1514
- reservation_arguments: '_ReserveArgs' = {
1525
+ reservation_arguments: _ReserveArgs = {
1515
1526
  'email': existing_reservation.email,
1516
1527
  'dates': (new_start, new_end),
1517
1528
  'data': existing_reservation.data,
@@ -1549,13 +1560,13 @@ class Scheduler(ContextServicesMixin):
1549
1560
  self,
1550
1561
  start: datetime,
1551
1562
  end: datetime,
1552
- days: _t.Optional[_t.Collection[_t.Union[Day, DayNumber]]] = None,
1563
+ days: Collection[Day | DayNumber] | None = None,
1553
1564
  minspots: int = 0,
1554
1565
  available_only: bool = False,
1555
- whole_day: _t.Literal['any', 'yes', 'no'] = 'any',
1556
- groups: _t.Literal['any', 'yes', 'no'] = 'any',
1566
+ whole_day: Literal['any', 'yes', 'no'] = 'any',
1567
+ groups: Literal['any', 'yes', 'no'] = 'any',
1557
1568
  strict: bool = False
1558
- ) -> _t.List[Allocation]:
1569
+ ) -> list[Allocation]:
1559
1570
  """ Search allocations using a number of options. The date is split
1560
1571
  into date/time. All allocations between start and end date within
1561
1572
  the given time (on each day) are included.
@@ -1623,7 +1634,7 @@ class Scheduler(ContextServicesMixin):
1623
1634
  assert whole_day in ('yes', 'no', 'any')
1624
1635
  assert groups in ('yes', 'no', 'any')
1625
1636
 
1626
- day_numbers: _t.Optional[_t.Set[DayNumber]]
1637
+ day_numbers: set[DayNumber] | None
1627
1638
  if days:
1628
1639
  # get the day from the map - if impossible take the verbatim value
1629
1640
  # this allows for using strings or integers
@@ -1657,9 +1668,11 @@ class Scheduler(ContextServicesMixin):
1657
1668
  if not allocation.overlaps(s, e):
1658
1669
  continue
1659
1670
 
1660
- if day_numbers:
1661
- if allocation.display_start().weekday() not in day_numbers:
1662
- continue
1671
+ if (
1672
+ day_numbers
1673
+ and allocation.display_start().weekday() not in day_numbers
1674
+ ):
1675
+ continue
1663
1676
 
1664
1677
  if whole_day != 'any':
1665
1678
  if whole_day == 'yes' and not allocation.whole_day:
@@ -1673,14 +1686,11 @@ class Scheduler(ContextServicesMixin):
1673
1686
  #
1674
1687
  # the spots are later checked again for actual availability, but
1675
1688
  # that is a heavier check, so it doesn't belong here.
1676
- if minspots:
1677
- if allocation.quota_limit > 0:
1678
- if allocation.quota_limit < minspots:
1679
- continue
1689
+ if minspots and (0 < allocation.quota_limit < minspots):
1690
+ continue
1680
1691
 
1681
- if available_only:
1682
- if not allocation.find_spot(s, e):
1683
- continue
1692
+ if available_only and not allocation.find_spot(s, e):
1693
+ continue
1684
1694
 
1685
1695
  if minspots:
1686
1696
  availability = self.availability(
@@ -1750,7 +1760,7 @@ class Scheduler(ContextServicesMixin):
1750
1760
  self,
1751
1761
  start: datetime,
1752
1762
  end: datetime
1753
- ) -> _t.List[Allocation]:
1763
+ ) -> list[Allocation]:
1754
1764
  """ Returns a list of allocations that are free within start and end.
1755
1765
  These allocations may come from the master or any of the mirrors.
1756
1766
 
@@ -1777,8 +1787,8 @@ class Scheduler(ContextServicesMixin):
1777
1787
  def reserved_slots_by_reservation(
1778
1788
  self,
1779
1789
  token: UUID,
1780
- id: _t.Optional[int] = None
1781
- ) -> 'Query[ReservedSlot]':
1790
+ id: int | None = None
1791
+ ) -> Query[ReservedSlot]:
1782
1792
  """ Returns all reserved slots of the given reservation.
1783
1793
  The id is optional and may be used only return the slots from a
1784
1794
  specific reservation matching token and id.
@@ -1796,7 +1806,7 @@ class Scheduler(ContextServicesMixin):
1796
1806
  ids = allocations.with_entities(Allocation.id)
1797
1807
  return query.filter(ReservedSlot.allocation_id.in_(ids))
1798
1808
 
1799
- def reservations_by_group(self, group: UUID) -> 'Query[Reservation]':
1809
+ def reservations_by_group(self, group: UUID) -> Query[Reservation]:
1800
1810
  tokens = self.managed_reservations().with_entities(Reservation.token)
1801
1811
  tokens = tokens.filter(Reservation.target == group)
1802
1812
 
@@ -1807,7 +1817,7 @@ class Scheduler(ContextServicesMixin):
1807
1817
  def reservations_by_allocation(
1808
1818
  self,
1809
1819
  allocation_id: int
1810
- ) -> 'Query[Reservation]':
1820
+ ) -> Query[Reservation]:
1811
1821
 
1812
1822
  master = self.allocation_by_id(allocation_id)
1813
1823
  return self.reservations_by_group(master.group)
@@ -1815,8 +1825,8 @@ class Scheduler(ContextServicesMixin):
1815
1825
  def reservations_by_token(
1816
1826
  self,
1817
1827
  token: UUID,
1818
- id: _t.Optional[int] = None
1819
- ) -> 'Query[Reservation]':
1828
+ id: int | None = None
1829
+ ) -> Query[Reservation]:
1820
1830
 
1821
1831
  query = self.managed_reservations()
1822
1832
  query = query.filter(Reservation.token == token)