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.
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import sedate
2
4
 
3
5
  from datetime import datetime, timedelta, time
@@ -24,18 +26,25 @@ from libres.modules.rasterizer import (
24
26
  )
25
27
 
26
28
 
27
- import typing as _t
28
- if _t.TYPE_CHECKING:
29
+ from typing import Any
30
+ from typing import TypeVar
31
+ from typing import TYPE_CHECKING
32
+ if TYPE_CHECKING:
29
33
  import uuid
34
+ from collections.abc import Iterator
30
35
  from sedate.types import TzInfoOrName
31
36
  from sqlalchemy.orm import Query
37
+ from typing import NamedTuple
32
38
  from typing_extensions import Self
33
39
 
34
- from libres.db.models import Reservation, ReservedSlot
40
+ from libres.db.models import ReservedSlot
35
41
  from libres.modules.rasterizer import Raster
36
42
 
37
- _OptDT1 = _t.TypeVar('_OptDT1', _t.Optional[datetime], datetime, None)
38
- _OptDT2 = _t.TypeVar('_OptDT2', _t.Optional[datetime], datetime, None)
43
+ _OptDT1 = TypeVar('_OptDT1', 'datetime | None', datetime, None)
44
+ _OptDT2 = TypeVar('_OptDT2', 'datetime | None', datetime, None)
45
+
46
+ class _ReservationIdRow(NamedTuple):
47
+ id: int
39
48
 
40
49
 
41
50
  class Allocation(TimestampMixin, ORMBase, OtherModels):
@@ -63,7 +72,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
63
72
  __tablename__ = 'allocations'
64
73
 
65
74
  #: the id of the allocation, autoincremented
66
- id: 'Column[int]' = Column(
75
+ id: Column[int] = Column(
67
76
  types.Integer(),
68
77
  primary_key=True,
69
78
  autoincrement=True
@@ -71,27 +80,27 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
71
80
 
72
81
  #: the resource uuid of the allocation, may not be an actual resource
73
82
  #: see :class:`.models.Allocation` for more information
74
- resource: 'Column[uuid.UUID]' = Column(UUID(), nullable=False)
83
+ resource: Column[uuid.UUID] = Column(UUID(), nullable=False)
75
84
 
76
85
  #: the polymorphic type of the allocation
77
- type: 'Column[_t.Optional[str]]' = Column(types.Text(), nullable=True)
86
+ type: Column[str | None] = Column(types.Text(), nullable=True)
78
87
 
79
88
  #: resource of which this allocation is a mirror. If the mirror_of
80
89
  #: attribute equals the resource, this is a real resource
81
90
  #: see :class:`.models.Allocation` for more information
82
- mirror_of: 'Column[uuid.UUID]' = Column(UUID(), nullable=False)
91
+ mirror_of: Column[uuid.UUID] = Column(UUID(), nullable=False)
83
92
 
84
93
  #: Group uuid to which this allocation belongs to. Every allocation has a
85
94
  #: group but some allocations may be the only one in their group.
86
- group: 'Column[uuid.UUID]' = Column(UUID(), nullable=False)
95
+ group: Column[uuid.UUID] = Column(UUID(), nullable=False)
87
96
 
88
97
  #: Number of times this allocation may be reserved
89
98
  # FIXME: Why is this not nullable=False? For now we pretend that it is
90
- quota: 'Column[int]' = Column(types.Integer(), default=1)
99
+ quota: Column[int] = Column(types.Integer(), default=1)
91
100
 
92
101
  #: Maximum number of times this allocation may be reserved with one
93
102
  #: single reservation.
94
- quota_limit: 'Column[int]' = Column(
103
+ quota_limit: Column[int] = Column(
95
104
  types.Integer(),
96
105
  default=0,
97
106
  nullable=False
@@ -99,37 +108,37 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
99
108
 
100
109
  #: Partly available allocations may be reserved partially. How They may
101
110
  #: be partitioned is defined by the allocation's raster.
102
- partly_available: 'Column[bool]' = Column(types.Boolean(), default=False)
111
+ partly_available: Column[bool] = Column(types.Boolean(), default=False)
103
112
 
104
113
  #: True if reservations for this allocation must be approved manually.
105
- approve_manually: 'Column[bool]' = Column(types.Boolean(), default=False)
114
+ approve_manually: Column[bool] = Column(types.Boolean(), default=False)
106
115
 
107
116
  #: The timezone this allocation resides in.
108
117
  # FIXME: Why is this not nullable=False? A lot of properties rely on this!
109
- timezone: 'Column[_t.Optional[str]]' = Column(types.String())
118
+ timezone: Column[str | None] = Column(types.String())
110
119
 
111
120
  #: Custom data reserved for the user
112
- data: 'Column[_t.Optional[_t.Any]]' = Column(
121
+ data: Column[dict[str, Any] | None] = Column(
113
122
  JSON(),
114
123
  nullable=True
115
124
  )
116
125
 
117
- _start: 'Column[datetime]' = Column(
126
+ _start: Column[datetime] = Column(
118
127
  UTCDateTime(timezone=False),
119
128
  nullable=False
120
129
  )
121
- _end: 'Column[datetime]' = Column(
130
+ _end: Column[datetime] = Column(
122
131
  UTCDateTime(timezone=False),
123
132
  nullable=False
124
133
  )
125
- _raster: 'Column[Raster]' = Column(
134
+ _raster: Column[Raster] = Column(
126
135
  types.Integer(), # type:ignore[arg-type]
127
136
  nullable=False
128
137
  )
129
138
 
130
- if _t.TYPE_CHECKING:
139
+ if TYPE_CHECKING:
131
140
  # forward declare backref
132
- reserved_slots: _t.List[ReservedSlot]
141
+ reserved_slots: list[ReservedSlot]
133
142
 
134
143
  __table_args__ = (
135
144
  Index('mirror_resource_ix', 'mirror_of', 'resource'),
@@ -149,7 +158,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
149
158
  def __hash__(self) -> int:
150
159
  return id(self)
151
160
 
152
- def copy(self) -> 'Self':
161
+ def copy(self) -> Self:
153
162
  """ Creates a new copy of this allocation. """
154
163
  allocation = self.__class__()
155
164
  allocation.resource = self.resource
@@ -180,7 +189,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
180
189
 
181
190
  #: The start of this allocation. Must be timezone aware.
182
191
  #: This date is rastered by the allocation's raster.
183
- if _t.TYPE_CHECKING:
192
+ if TYPE_CHECKING:
184
193
  # NOTE: type checkers perform some special sauce for property
185
194
  # so the non-decorator style isn't well supported
186
195
  @property
@@ -209,7 +218,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
209
218
  #: to avoid overlaps with other allocations.
210
219
  #: That is to say an allocation that ends at 15:00 really ends at
211
220
  #: 14:59:59.999999
212
- if _t.TYPE_CHECKING:
221
+ if TYPE_CHECKING:
213
222
  # NOTE: type checkers perform some special sauce for property
214
223
  # so the non-decorator style isn't well supported
215
224
  @property
@@ -219,10 +228,10 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
219
228
  else:
220
229
  end = property(get_end, set_end)
221
230
 
222
- def get_raster(self) -> 'Raster':
231
+ def get_raster(self) -> Raster:
223
232
  return self._raster
224
233
 
225
- def set_raster(self, raster: 'Raster') -> None:
234
+ def set_raster(self, raster: Raster) -> None:
226
235
  # the raster can only be set once!
227
236
  assert not self._raster
228
237
  self._raster = raster # type:ignore[unreachable]
@@ -239,7 +248,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
239
248
  if self._end:
240
249
  self._end = rasterize_end(self._end, self.raster)
241
250
 
242
- if _t.TYPE_CHECKING:
251
+ if TYPE_CHECKING:
243
252
  # NOTE: type checkers perform some special sauce for property
244
253
  # so the non-decorator style isn't well supported
245
254
  @property
@@ -251,7 +260,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
251
260
 
252
261
  def display_start(
253
262
  self,
254
- timezone: _t.Optional['TzInfoOrName'] = None
263
+ timezone: TzInfoOrName | None = None
255
264
  ) -> datetime:
256
265
  """Returns the start in either the timezone given or the timezone
257
266
  on the allocation."""
@@ -263,7 +272,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
263
272
 
264
273
  def display_end(
265
274
  self,
266
- timezone: _t.Optional['TzInfoOrName'] = None
275
+ timezone: TzInfoOrName | None = None
267
276
  ) -> datetime:
268
277
  """Returns the end plus one microsecond in either the timezone given
269
278
  or the timezone on the allocation.
@@ -278,9 +287,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
278
287
 
279
288
  def _prepare_range(
280
289
  self,
281
- start: '_OptDT1',
282
- end: '_OptDT2'
283
- ) -> _t.Tuple['_OptDT1', '_OptDT2']:
290
+ start: _OptDT1,
291
+ end: _OptDT2
292
+ ) -> tuple[_OptDT1, _OptDT2]:
284
293
 
285
294
  if start:
286
295
  assert self.timezone is not None
@@ -328,9 +337,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
328
337
 
329
338
  def free_slots(
330
339
  self,
331
- start: _t.Optional[datetime] = None,
332
- end: _t.Optional[datetime] = None
333
- ) -> _t.List[_t.Tuple[datetime, datetime]]:
340
+ start: datetime | None = None,
341
+ end: datetime | None = None
342
+ ) -> list[tuple[datetime, datetime]]:
334
343
  """ Returns the slots which are not yet reserved. """
335
344
  reserved = {slot.start for slot in self.reserved_slots}
336
345
 
@@ -342,9 +351,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
342
351
 
343
352
  def align_dates(
344
353
  self,
345
- start: _t.Optional[datetime] = None,
346
- end: _t.Optional[datetime] = None
347
- ) -> _t.Tuple[datetime, datetime]:
354
+ start: datetime | None = None,
355
+ end: datetime | None = None
356
+ ) -> tuple[datetime, datetime]:
348
357
  """ Aligns the given dates to the start and end date of the allocation.
349
358
 
350
359
  """
@@ -361,9 +370,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
361
370
 
362
371
  def all_slots(
363
372
  self,
364
- start: _t.Optional[datetime] = None,
365
- end: _t.Optional[datetime] = None
366
- ) -> '_t.Iterator[_t.Tuple[datetime, datetime]]':
373
+ start: datetime | None = None,
374
+ end: datetime | None = None
375
+ ) -> Iterator[tuple[datetime, datetime]]:
367
376
  """ Returns the slots which exist with this timespan. Reserved or free.
368
377
 
369
378
  """
@@ -376,8 +385,8 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
376
385
 
377
386
  def count_slots(
378
387
  self,
379
- start: _t.Optional[datetime] = None,
380
- end: _t.Optional[datetime] = None
388
+ start: datetime | None = None,
389
+ end: datetime | None = None
381
390
  ) -> int:
382
391
  """ Returns the number of slots which exist with this timespan.
383
392
  Reserved or free.
@@ -393,8 +402,8 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
393
402
 
394
403
  def is_available(
395
404
  self,
396
- start: _t.Optional[datetime] = None,
397
- end: _t.Optional[datetime] = None
405
+ start: datetime | None = None,
406
+ end: datetime | None = None
398
407
  ) -> bool:
399
408
  """ Returns true if the given daterange is completely available. """
400
409
 
@@ -414,11 +423,11 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
414
423
  self,
415
424
  start: time,
416
425
  end: time,
417
- timezone: _t.Optional['TzInfoOrName'] = None,
426
+ timezone: TzInfoOrName | None = None,
418
427
  is_dst: bool = False,
419
428
  raise_non_existent: bool = False,
420
429
  raise_ambiguous: bool = False
421
- ) -> _t.Tuple[datetime, datetime]:
430
+ ) -> tuple[datetime, datetime]:
422
431
  """ Takes the given timespan and moves the start/end date to
423
432
  the closest reservable slot. So if 10:00 - 11:00 is requested it will
424
433
 
@@ -465,7 +474,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
465
474
  return self.display_start(timezone), self.display_end(timezone)
466
475
 
467
476
  @property
468
- def pending_reservations(self) -> 'Query[Reservation]':
477
+ def pending_reservations(self) -> Query[_ReservationIdRow]:
469
478
  """ Returns the pending reservations query for this allocation.
470
479
  As the pending reservations target the group and not a specific
471
480
  allocation this function returns the same value for masters and
@@ -476,7 +485,8 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
476
485
  "Don't call if the allocation does not yet exist"
477
486
  )
478
487
 
479
- Reservation = self.models.Reservation
488
+ Reservation = self.models.Reservation # noqa: N806
489
+ query: Query[_ReservationIdRow]
480
490
  query = object_session(self).query(Reservation.id)
481
491
  query = query.filter(Reservation.target == self.group)
482
492
  query = query.filter(Reservation.status == 'pending')
@@ -584,7 +594,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
584
594
  self,
585
595
  start: datetime,
586
596
  end: datetime
587
- ) -> _t.Optional['Self ']:
597
+ ) -> Self | None:
588
598
  """ Returns the first free allocation spot amongst the master and the
589
599
  mirrors. Honors the quota set on the master and will only try the
590
600
  master if the quota is set to 1.
@@ -623,10 +633,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
623
633
 
624
634
  return True
625
635
 
626
- def normalized_slots(self) -> _t.Iterator[_t.Union[
627
- _t.Tuple[datetime, datetime],
628
- _t.Tuple[None, None]
629
- ]]:
636
+ def normalized_slots(self) -> Iterator[
637
+ tuple[datetime, datetime] | tuple[None, None]
638
+ ]:
630
639
  """Most of the times this will return the same thing as all_slots
631
640
  however for DST timezones it will ensure the transitions days with
632
641
  23 and 25 hours respectively still return 24 hours worth of slots.
@@ -690,7 +699,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
690
699
  def availability_partitions(
691
700
  self,
692
701
  normalize_dst: bool = True
693
- ) -> _t.List[_t.Tuple[float, bool]]:
702
+ ) -> list[tuple[float, bool]]:
694
703
  """Partitions the space between start and end into blocks of either
695
704
  free or reserved time. Each block has a percentage representing the
696
705
  space the block occupies compared to the size of the whole allocation.
@@ -786,17 +795,19 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
786
795
 
787
796
  return self.resource == self.mirror_of
788
797
 
789
- def get_master(self) -> 'Self':
798
+ def get_master(self) -> Self:
790
799
  if self.is_master:
791
800
  return self
792
801
  else:
793
- query = object_session(self).query(Allocation)
802
+ # FIXME: This should either query `self.__class__` or
803
+ # we need to return `Allocation` rather than `Self`
804
+ query: Query[Self] = object_session(self).query(Allocation)
794
805
  query = query.filter(Allocation._start == self._start)
795
806
  query = query.filter(Allocation.resource == self.mirror_of)
796
807
 
797
808
  return query.one()
798
809
 
799
- def siblings(self, imaginary: bool = True) -> _t.List['Self']:
810
+ def siblings(self, imaginary: bool = True) -> list[Self]:
800
811
  """Returns the master/mirrors group this allocation is part of.
801
812
 
802
813
  If 'imaginary' is true, inexistant mirrors are created on the fly.
@@ -814,7 +825,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
814
825
  assert self.is_master
815
826
  return [self]
816
827
 
817
- query = object_session(self).query(Allocation)
828
+ # FIXME: This should either query `self.__class__` or
829
+ # we need to return `Allocation` rather than `Self`
830
+ query: Query[Self] = object_session(self).query(Allocation)
818
831
  query = query.filter(Allocation.mirror_of == self.mirror_of)
819
832
  query = query.filter(Allocation._start == self._start)
820
833
 
libres/db/models/base.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from sqlalchemy.ext import declarative
2
4
 
3
5
  ORMBase = declarative.declarative_base()
libres/db/models/other.py CHANGED
@@ -1,11 +1,16 @@
1
- import typing as _t
2
- if _t.TYPE_CHECKING:
1
+ from __future__ import annotations
2
+
3
+
4
+ from typing import TYPE_CHECKING
5
+ if TYPE_CHECKING:
6
+ from typing import Protocol
7
+
3
8
  import libres.db.models as _models
4
9
 
5
- class _Models(_t.Protocol):
6
- Allocation: _t.Type[_models.Allocation]
7
- ReservedSlot: _t.Type[_models.ReservedSlot]
8
- Reservation: _t.Type[_models.Reservation]
10
+ class _Models(Protocol):
11
+ Allocation: type[_models.Allocation]
12
+ ReservedSlot: type[_models.ReservedSlot]
13
+ Reservation: type[_models.Reservation]
9
14
 
10
15
 
11
16
  models = None
@@ -16,7 +21,7 @@ class OtherModels:
16
21
  classes without causing circular imports. """
17
22
 
18
23
  @property
19
- def models(self) -> '_Models':
24
+ def models(self) -> _Models:
20
25
  global models
21
26
  if not models:
22
27
  # FIXME: libres.db exports ORMBase, do we really
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import sedate
2
4
 
3
5
  from datetime import datetime, timedelta
@@ -13,8 +15,11 @@ from libres.db.models.other import OtherModels
13
15
  from libres.db.models.timestamp import TimestampMixin
14
16
 
15
17
 
16
- import typing as _t
17
- if _t.TYPE_CHECKING:
18
+ from typing import Any
19
+ from typing import Literal
20
+ from typing import NamedTuple
21
+ from typing import TYPE_CHECKING
22
+ if TYPE_CHECKING:
18
23
  import uuid
19
24
  from sedate.types import TzInfoOrName
20
25
  from sqlalchemy.orm import Query
@@ -22,7 +27,7 @@ if _t.TYPE_CHECKING:
22
27
  from libres.db.models import Allocation
23
28
 
24
29
 
25
- class Timespan(_t.NamedTuple):
30
+ class Timespan(NamedTuple):
26
31
  start: datetime
27
32
  end: datetime
28
33
 
@@ -34,23 +39,23 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
34
39
 
35
40
  __tablename__ = 'reservations'
36
41
 
37
- id: 'Column[int]' = Column(
42
+ id: Column[int] = Column(
38
43
  types.Integer(),
39
44
  primary_key=True,
40
45
  autoincrement=True
41
46
  )
42
47
 
43
- token: 'Column[uuid.UUID]' = Column(
48
+ token: Column[uuid.UUID] = Column(
44
49
  UUID(),
45
50
  nullable=False,
46
51
  )
47
52
 
48
- target: 'Column[uuid.UUID]' = Column(
53
+ target: Column[uuid.UUID] = Column(
49
54
  UUID(),
50
55
  nullable=False,
51
56
  )
52
57
 
53
- target_type: 'Column[_t.Literal["group", "allocation"]]' = Column(
58
+ target_type: Column[Literal['group', 'allocation']] = Column(
54
59
  types.Enum( # type:ignore[arg-type]
55
60
  'group', 'allocation',
56
61
  name='reservation_target_type'
@@ -58,56 +63,56 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
58
63
  nullable=False
59
64
  )
60
65
 
61
- type: 'Column[_t.Optional[str]]' = Column(
66
+ type: Column[str | None] = Column(
62
67
  types.Text(),
63
68
  nullable=True
64
69
  )
65
70
 
66
- resource: 'Column[uuid.UUID]' = Column(
71
+ resource: Column[uuid.UUID] = Column(
67
72
  UUID(),
68
73
  nullable=False
69
74
  )
70
75
 
71
- start: 'Column[_t.Optional[datetime]]' = Column(
76
+ start: Column[datetime | None] = Column(
72
77
  UTCDateTime(timezone=False),
73
78
  nullable=True
74
79
  )
75
80
 
76
- end: 'Column[_t.Optional[datetime]]' = Column(
81
+ end: Column[datetime | None] = Column(
77
82
  UTCDateTime(timezone=False),
78
83
  nullable=True
79
84
  )
80
85
 
81
- timezone: 'Column[_t.Optional[str]]' = Column(
86
+ timezone: Column[str | None] = Column(
82
87
  types.String(),
83
88
  nullable=True
84
89
  )
85
90
 
86
- status: 'Column[_t.Literal["pending", "approved"]]' = Column(
91
+ status: Column[Literal['pending', 'approved']] = Column(
87
92
  types.Enum( # type:ignore[arg-type]
88
93
  'pending', 'approved',
89
- name="reservation_status"
94
+ name='reservation_status'
90
95
  ),
91
96
  nullable=False
92
97
  )
93
98
 
94
- data: 'Column[_t.Optional[_t.Any]]' = deferred(
99
+ data: Column[dict[str, Any] | None] = deferred(
95
100
  Column(
96
101
  JSON(),
97
102
  nullable=True
98
103
  )
99
104
  )
100
105
 
101
- email: 'Column[str]' = Column(
106
+ email: Column[str] = Column(
102
107
  types.Unicode(254),
103
108
  nullable=False
104
109
  )
105
110
 
106
- session_id: 'Column[_t.Optional[uuid.UUID]]' = Column(
111
+ session_id: Column[uuid.UUID | None] = Column(
107
112
  UUID()
108
113
  )
109
114
 
110
- quota: 'Column[int]' = Column(
115
+ quota: Column[int] = Column(
111
116
  types.Integer(),
112
117
  nullable=False
113
118
  )
@@ -121,7 +126,7 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
121
126
  'polymorphic_on': type
122
127
  }
123
128
 
124
- def _target_allocations(self) -> 'Query[Allocation]':
129
+ def _target_allocations(self) -> Query[Allocation]:
125
130
  """ Returns the allocations this reservation is targeting. This should
126
131
  NOT be confused with db.allocations_by_reservation. The method in
127
132
  the db module returns the actual allocations belonging to an approved
@@ -133,7 +138,7 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
133
138
  be dangerous.
134
139
 
135
140
  """
136
- Allocation = self.models.Allocation
141
+ Allocation = self.models.Allocation # noqa: N806
137
142
  query = object_session(self).query(Allocation)
138
143
  query = query.filter(Allocation.group == self.target)
139
144
 
@@ -143,11 +148,11 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
143
148
  # order by date
144
149
  query = query.order_by(Allocation._start)
145
150
 
146
- return query
151
+ return query # type: ignore[no-any-return]
147
152
 
148
153
  def display_start(
149
154
  self,
150
- timezone: _t.Optional['TzInfoOrName'] = None
155
+ timezone: TzInfoOrName | None = None
151
156
  ) -> datetime:
152
157
  """Does nothing but to form a nice pair to display_end."""
153
158
  assert self.start is not None
@@ -158,7 +163,7 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
158
163
 
159
164
  def display_end(
160
165
  self,
161
- timezone: _t.Optional['TzInfoOrName'] = None
166
+ timezone: TzInfoOrName | None = None
162
167
  ) -> datetime:
163
168
  """Returns the end plus one microsecond (nicer display)."""
164
169
  assert self.end is not None
@@ -169,7 +174,7 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
169
174
  end = self.end + timedelta(microseconds=1)
170
175
  return sedate.to_timezone(end, timezone)
171
176
 
172
- def timespans(self) -> _t.List[Timespan]:
177
+ def timespans(self) -> list[Timespan]:
173
178
  """ Returns the timespans targeted by this reservation.
174
179
 
175
180
  The result is a list of :class:`~libres.db.models.reservation.Timespan`
@@ -207,4 +212,4 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
207
212
  # A reservation is deemed autoapprovable if no allocation
208
213
  # requires explicit approval
209
214
 
210
- return object_session(self).query(~query.exists()).scalar()
215
+ return object_session(self).query(~query.exists()).scalar() # type: ignore[no-any-return]
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import sedate
2
4
 
3
5
  from datetime import datetime, timedelta
@@ -18,8 +20,8 @@ from libres.db.models.types import UUID, UTCDateTime
18
20
  from libres.db.models.timestamp import TimestampMixin
19
21
 
20
22
 
21
- import typing as _t
22
- if _t.TYPE_CHECKING:
23
+ from typing import TYPE_CHECKING
24
+ if TYPE_CHECKING:
23
25
  import uuid
24
26
  from sedate.types import TzInfoOrName
25
27
 
@@ -29,32 +31,32 @@ class ReservedSlot(TimestampMixin, ORMBase):
29
31
 
30
32
  __tablename__ = 'reserved_slots'
31
33
 
32
- resource: 'Column[uuid.UUID]' = Column(
34
+ resource: Column[uuid.UUID] = Column(
33
35
  UUID(),
34
36
  primary_key=True,
35
37
  nullable=False,
36
38
  autoincrement=False
37
39
  )
38
40
 
39
- start: 'Column[datetime]' = Column(
41
+ start: Column[datetime] = Column(
40
42
  UTCDateTime(timezone=False),
41
43
  primary_key=True,
42
44
  nullable=False,
43
45
  autoincrement=False
44
46
  )
45
47
 
46
- end: 'Column[datetime]' = Column(
48
+ end: Column[datetime] = Column(
47
49
  UTCDateTime(timezone=False),
48
50
  nullable=False
49
51
  )
50
52
 
51
- allocation_id: 'Column[int]' = Column(
53
+ allocation_id: Column[int] = Column(
52
54
  types.Integer(),
53
55
  ForeignKey(Allocation.id),
54
56
  nullable=False
55
57
  )
56
58
 
57
- allocation: 'relationship[Allocation]' = relationship(
59
+ allocation: relationship[Allocation] = relationship(
58
60
  Allocation,
59
61
  primaryjoin=Allocation.id == allocation_id,
60
62
 
@@ -68,7 +70,7 @@ class ReservedSlot(TimestampMixin, ORMBase):
68
70
  )
69
71
  )
70
72
 
71
- reservation_token: 'Column[uuid.UUID]' = Column(
73
+ reservation_token: Column[uuid.UUID] = Column(
72
74
  UUID(),
73
75
  nullable=False
74
76
  )
@@ -79,7 +81,7 @@ class ReservedSlot(TimestampMixin, ORMBase):
79
81
 
80
82
  def display_start(
81
83
  self,
82
- timezone: _t.Optional['TzInfoOrName'] = None
84
+ timezone: TzInfoOrName | None = None
83
85
  ) -> datetime:
84
86
 
85
87
  if timezone is None:
@@ -91,7 +93,7 @@ class ReservedSlot(TimestampMixin, ORMBase):
91
93
 
92
94
  def display_end(
93
95
  self,
94
- timezone: _t.Optional['TzInfoOrName'] = None
96
+ timezone: TzInfoOrName | None = None
95
97
  ) -> datetime:
96
98
 
97
99
  if timezone is None: