libres 0.7.2__py3-none-any.whl → 0.8.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/__init__.py +3 -1
- libres/context/core.py +32 -26
- libres/context/exposure.py +7 -3
- libres/context/registry.py +14 -10
- libres/context/session.py +8 -6
- libres/context/settings.py +9 -6
- libres/db/__init__.py +3 -1
- libres/db/models/__init__.py +2 -0
- libres/db/models/allocation.py +60 -56
- libres/db/models/base.py +2 -0
- libres/db/models/other.py +12 -7
- libres/db/models/reservation.py +28 -23
- libres/db/models/reserved_slot.py +12 -10
- libres/db/models/timestamp.py +9 -7
- libres/db/models/types/__init__.py +2 -0
- libres/db/models/types/json_type.py +12 -9
- libres/db/models/types/utcdatetime.py +10 -8
- libres/db/models/types/uuid_type.py +10 -8
- libres/db/queries.py +32 -27
- libres/db/scheduler.py +133 -127
- libres/modules/__init__.py +0 -1
- libres/modules/errors.py +9 -7
- libres/modules/events.py +57 -56
- libres/modules/rasterizer.py +11 -7
- libres/modules/utils.py +16 -14
- {libres-0.7.2.dist-info → libres-0.8.0.dist-info}/METADATA +50 -26
- libres-0.8.0.dist-info/RECORD +33 -0
- {libres-0.7.2.dist-info → libres-0.8.0.dist-info}/WHEEL +1 -1
- libres-0.7.2.dist-info/RECORD +0 -33
- {libres-0.7.2.dist-info → libres-0.8.0.dist-info}/LICENSE +0 -0
- {libres-0.7.2.dist-info → libres-0.8.0.dist-info}/top_level.txt +0 -0
libres/db/models/allocation.py
CHANGED
|
@@ -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,9 +26,12 @@ from libres.modules.rasterizer import (
|
|
|
24
26
|
)
|
|
25
27
|
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
32
37
|
from typing_extensions import Self
|
|
@@ -34,8 +39,8 @@ if _t.TYPE_CHECKING:
|
|
|
34
39
|
from libres.db.models import Reservation, ReservedSlot
|
|
35
40
|
from libres.modules.rasterizer import Raster
|
|
36
41
|
|
|
37
|
-
_OptDT1 =
|
|
38
|
-
_OptDT2 =
|
|
42
|
+
_OptDT1 = TypeVar('_OptDT1', 'datetime | None', datetime, None)
|
|
43
|
+
_OptDT2 = TypeVar('_OptDT2', 'datetime | None', datetime, None)
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
@@ -63,7 +68,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
63
68
|
__tablename__ = 'allocations'
|
|
64
69
|
|
|
65
70
|
#: the id of the allocation, autoincremented
|
|
66
|
-
id:
|
|
71
|
+
id: Column[int] = Column(
|
|
67
72
|
types.Integer(),
|
|
68
73
|
primary_key=True,
|
|
69
74
|
autoincrement=True
|
|
@@ -71,27 +76,27 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
71
76
|
|
|
72
77
|
#: the resource uuid of the allocation, may not be an actual resource
|
|
73
78
|
#: see :class:`.models.Allocation` for more information
|
|
74
|
-
resource:
|
|
79
|
+
resource: Column[uuid.UUID] = Column(UUID(), nullable=False)
|
|
75
80
|
|
|
76
81
|
#: the polymorphic type of the allocation
|
|
77
|
-
type:
|
|
82
|
+
type: Column[str | None] = Column(types.Text(), nullable=True)
|
|
78
83
|
|
|
79
84
|
#: resource of which this allocation is a mirror. If the mirror_of
|
|
80
85
|
#: attribute equals the resource, this is a real resource
|
|
81
86
|
#: see :class:`.models.Allocation` for more information
|
|
82
|
-
mirror_of:
|
|
87
|
+
mirror_of: Column[uuid.UUID] = Column(UUID(), nullable=False)
|
|
83
88
|
|
|
84
89
|
#: Group uuid to which this allocation belongs to. Every allocation has a
|
|
85
90
|
#: group but some allocations may be the only one in their group.
|
|
86
|
-
group:
|
|
91
|
+
group: Column[uuid.UUID] = Column(UUID(), nullable=False)
|
|
87
92
|
|
|
88
93
|
#: Number of times this allocation may be reserved
|
|
89
94
|
# FIXME: Why is this not nullable=False? For now we pretend that it is
|
|
90
|
-
quota:
|
|
95
|
+
quota: Column[int] = Column(types.Integer(), default=1)
|
|
91
96
|
|
|
92
97
|
#: Maximum number of times this allocation may be reserved with one
|
|
93
98
|
#: single reservation.
|
|
94
|
-
quota_limit:
|
|
99
|
+
quota_limit: Column[int] = Column(
|
|
95
100
|
types.Integer(),
|
|
96
101
|
default=0,
|
|
97
102
|
nullable=False
|
|
@@ -99,37 +104,37 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
99
104
|
|
|
100
105
|
#: Partly available allocations may be reserved partially. How They may
|
|
101
106
|
#: be partitioned is defined by the allocation's raster.
|
|
102
|
-
partly_available:
|
|
107
|
+
partly_available: Column[bool] = Column(types.Boolean(), default=False)
|
|
103
108
|
|
|
104
109
|
#: True if reservations for this allocation must be approved manually.
|
|
105
|
-
approve_manually:
|
|
110
|
+
approve_manually: Column[bool] = Column(types.Boolean(), default=False)
|
|
106
111
|
|
|
107
112
|
#: The timezone this allocation resides in.
|
|
108
113
|
# FIXME: Why is this not nullable=False? A lot of properties rely on this!
|
|
109
|
-
timezone:
|
|
114
|
+
timezone: Column[str | None] = Column(types.String())
|
|
110
115
|
|
|
111
116
|
#: Custom data reserved for the user
|
|
112
|
-
data:
|
|
117
|
+
data: Column[Any | None] = Column(
|
|
113
118
|
JSON(),
|
|
114
119
|
nullable=True
|
|
115
120
|
)
|
|
116
121
|
|
|
117
|
-
_start:
|
|
122
|
+
_start: Column[datetime] = Column(
|
|
118
123
|
UTCDateTime(timezone=False),
|
|
119
124
|
nullable=False
|
|
120
125
|
)
|
|
121
|
-
_end:
|
|
126
|
+
_end: Column[datetime] = Column(
|
|
122
127
|
UTCDateTime(timezone=False),
|
|
123
128
|
nullable=False
|
|
124
129
|
)
|
|
125
|
-
_raster:
|
|
130
|
+
_raster: Column[Raster] = Column(
|
|
126
131
|
types.Integer(), # type:ignore[arg-type]
|
|
127
132
|
nullable=False
|
|
128
133
|
)
|
|
129
134
|
|
|
130
|
-
if
|
|
135
|
+
if TYPE_CHECKING:
|
|
131
136
|
# forward declare backref
|
|
132
|
-
reserved_slots:
|
|
137
|
+
reserved_slots: list[ReservedSlot]
|
|
133
138
|
|
|
134
139
|
__table_args__ = (
|
|
135
140
|
Index('mirror_resource_ix', 'mirror_of', 'resource'),
|
|
@@ -149,7 +154,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
149
154
|
def __hash__(self) -> int:
|
|
150
155
|
return id(self)
|
|
151
156
|
|
|
152
|
-
def copy(self) ->
|
|
157
|
+
def copy(self) -> Self:
|
|
153
158
|
""" Creates a new copy of this allocation. """
|
|
154
159
|
allocation = self.__class__()
|
|
155
160
|
allocation.resource = self.resource
|
|
@@ -180,7 +185,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
180
185
|
|
|
181
186
|
#: The start of this allocation. Must be timezone aware.
|
|
182
187
|
#: This date is rastered by the allocation's raster.
|
|
183
|
-
if
|
|
188
|
+
if TYPE_CHECKING:
|
|
184
189
|
# NOTE: type checkers perform some special sauce for property
|
|
185
190
|
# so the non-decorator style isn't well supported
|
|
186
191
|
@property
|
|
@@ -209,7 +214,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
209
214
|
#: to avoid overlaps with other allocations.
|
|
210
215
|
#: That is to say an allocation that ends at 15:00 really ends at
|
|
211
216
|
#: 14:59:59.999999
|
|
212
|
-
if
|
|
217
|
+
if TYPE_CHECKING:
|
|
213
218
|
# NOTE: type checkers perform some special sauce for property
|
|
214
219
|
# so the non-decorator style isn't well supported
|
|
215
220
|
@property
|
|
@@ -219,10 +224,10 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
219
224
|
else:
|
|
220
225
|
end = property(get_end, set_end)
|
|
221
226
|
|
|
222
|
-
def get_raster(self) ->
|
|
227
|
+
def get_raster(self) -> Raster:
|
|
223
228
|
return self._raster
|
|
224
229
|
|
|
225
|
-
def set_raster(self, raster:
|
|
230
|
+
def set_raster(self, raster: Raster) -> None:
|
|
226
231
|
# the raster can only be set once!
|
|
227
232
|
assert not self._raster
|
|
228
233
|
self._raster = raster # type:ignore[unreachable]
|
|
@@ -239,7 +244,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
239
244
|
if self._end:
|
|
240
245
|
self._end = rasterize_end(self._end, self.raster)
|
|
241
246
|
|
|
242
|
-
if
|
|
247
|
+
if TYPE_CHECKING:
|
|
243
248
|
# NOTE: type checkers perform some special sauce for property
|
|
244
249
|
# so the non-decorator style isn't well supported
|
|
245
250
|
@property
|
|
@@ -251,7 +256,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
251
256
|
|
|
252
257
|
def display_start(
|
|
253
258
|
self,
|
|
254
|
-
timezone:
|
|
259
|
+
timezone: TzInfoOrName | None = None
|
|
255
260
|
) -> datetime:
|
|
256
261
|
"""Returns the start in either the timezone given or the timezone
|
|
257
262
|
on the allocation."""
|
|
@@ -263,7 +268,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
263
268
|
|
|
264
269
|
def display_end(
|
|
265
270
|
self,
|
|
266
|
-
timezone:
|
|
271
|
+
timezone: TzInfoOrName | None = None
|
|
267
272
|
) -> datetime:
|
|
268
273
|
"""Returns the end plus one microsecond in either the timezone given
|
|
269
274
|
or the timezone on the allocation.
|
|
@@ -278,9 +283,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
278
283
|
|
|
279
284
|
def _prepare_range(
|
|
280
285
|
self,
|
|
281
|
-
start:
|
|
282
|
-
end:
|
|
283
|
-
) ->
|
|
286
|
+
start: _OptDT1,
|
|
287
|
+
end: _OptDT2
|
|
288
|
+
) -> tuple[_OptDT1, _OptDT2]:
|
|
284
289
|
|
|
285
290
|
if start:
|
|
286
291
|
assert self.timezone is not None
|
|
@@ -328,9 +333,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
328
333
|
|
|
329
334
|
def free_slots(
|
|
330
335
|
self,
|
|
331
|
-
start:
|
|
332
|
-
end:
|
|
333
|
-
) ->
|
|
336
|
+
start: datetime | None = None,
|
|
337
|
+
end: datetime | None = None
|
|
338
|
+
) -> list[tuple[datetime, datetime]]:
|
|
334
339
|
""" Returns the slots which are not yet reserved. """
|
|
335
340
|
reserved = {slot.start for slot in self.reserved_slots}
|
|
336
341
|
|
|
@@ -342,9 +347,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
342
347
|
|
|
343
348
|
def align_dates(
|
|
344
349
|
self,
|
|
345
|
-
start:
|
|
346
|
-
end:
|
|
347
|
-
) ->
|
|
350
|
+
start: datetime | None = None,
|
|
351
|
+
end: datetime | None = None
|
|
352
|
+
) -> tuple[datetime, datetime]:
|
|
348
353
|
""" Aligns the given dates to the start and end date of the allocation.
|
|
349
354
|
|
|
350
355
|
"""
|
|
@@ -361,9 +366,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
361
366
|
|
|
362
367
|
def all_slots(
|
|
363
368
|
self,
|
|
364
|
-
start:
|
|
365
|
-
end:
|
|
366
|
-
) ->
|
|
369
|
+
start: datetime | None = None,
|
|
370
|
+
end: datetime | None = None
|
|
371
|
+
) -> Iterator[tuple[datetime, datetime]]:
|
|
367
372
|
""" Returns the slots which exist with this timespan. Reserved or free.
|
|
368
373
|
|
|
369
374
|
"""
|
|
@@ -376,8 +381,8 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
376
381
|
|
|
377
382
|
def count_slots(
|
|
378
383
|
self,
|
|
379
|
-
start:
|
|
380
|
-
end:
|
|
384
|
+
start: datetime | None = None,
|
|
385
|
+
end: datetime | None = None
|
|
381
386
|
) -> int:
|
|
382
387
|
""" Returns the number of slots which exist with this timespan.
|
|
383
388
|
Reserved or free.
|
|
@@ -393,8 +398,8 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
393
398
|
|
|
394
399
|
def is_available(
|
|
395
400
|
self,
|
|
396
|
-
start:
|
|
397
|
-
end:
|
|
401
|
+
start: datetime | None = None,
|
|
402
|
+
end: datetime | None = None
|
|
398
403
|
) -> bool:
|
|
399
404
|
""" Returns true if the given daterange is completely available. """
|
|
400
405
|
|
|
@@ -414,11 +419,11 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
414
419
|
self,
|
|
415
420
|
start: time,
|
|
416
421
|
end: time,
|
|
417
|
-
timezone:
|
|
422
|
+
timezone: TzInfoOrName | None = None,
|
|
418
423
|
is_dst: bool = False,
|
|
419
424
|
raise_non_existent: bool = False,
|
|
420
425
|
raise_ambiguous: bool = False
|
|
421
|
-
) ->
|
|
426
|
+
) -> tuple[datetime, datetime]:
|
|
422
427
|
""" Takes the given timespan and moves the start/end date to
|
|
423
428
|
the closest reservable slot. So if 10:00 - 11:00 is requested it will
|
|
424
429
|
|
|
@@ -465,7 +470,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
465
470
|
return self.display_start(timezone), self.display_end(timezone)
|
|
466
471
|
|
|
467
472
|
@property
|
|
468
|
-
def pending_reservations(self) ->
|
|
473
|
+
def pending_reservations(self) -> Query[Reservation]:
|
|
469
474
|
""" Returns the pending reservations query for this allocation.
|
|
470
475
|
As the pending reservations target the group and not a specific
|
|
471
476
|
allocation this function returns the same value for masters and
|
|
@@ -476,7 +481,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
476
481
|
"Don't call if the allocation does not yet exist"
|
|
477
482
|
)
|
|
478
483
|
|
|
479
|
-
Reservation = self.models.Reservation
|
|
484
|
+
Reservation = self.models.Reservation # noqa: N806
|
|
480
485
|
query = object_session(self).query(Reservation.id)
|
|
481
486
|
query = query.filter(Reservation.target == self.group)
|
|
482
487
|
query = query.filter(Reservation.status == 'pending')
|
|
@@ -584,7 +589,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
584
589
|
self,
|
|
585
590
|
start: datetime,
|
|
586
591
|
end: datetime
|
|
587
|
-
) ->
|
|
592
|
+
) -> Self | None:
|
|
588
593
|
""" Returns the first free allocation spot amongst the master and the
|
|
589
594
|
mirrors. Honors the quota set on the master and will only try the
|
|
590
595
|
master if the quota is set to 1.
|
|
@@ -623,10 +628,9 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
623
628
|
|
|
624
629
|
return True
|
|
625
630
|
|
|
626
|
-
def normalized_slots(self) ->
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
]]:
|
|
631
|
+
def normalized_slots(self) -> Iterator[
|
|
632
|
+
tuple[datetime, datetime] | tuple[None, None]
|
|
633
|
+
]:
|
|
630
634
|
"""Most of the times this will return the same thing as all_slots
|
|
631
635
|
however for DST timezones it will ensure the transitions days with
|
|
632
636
|
23 and 25 hours respectively still return 24 hours worth of slots.
|
|
@@ -690,7 +694,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
690
694
|
def availability_partitions(
|
|
691
695
|
self,
|
|
692
696
|
normalize_dst: bool = True
|
|
693
|
-
) ->
|
|
697
|
+
) -> list[tuple[float, bool]]:
|
|
694
698
|
"""Partitions the space between start and end into blocks of either
|
|
695
699
|
free or reserved time. Each block has a percentage representing the
|
|
696
700
|
space the block occupies compared to the size of the whole allocation.
|
|
@@ -786,7 +790,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
786
790
|
|
|
787
791
|
return self.resource == self.mirror_of
|
|
788
792
|
|
|
789
|
-
def get_master(self) ->
|
|
793
|
+
def get_master(self) -> Self:
|
|
790
794
|
if self.is_master:
|
|
791
795
|
return self
|
|
792
796
|
else:
|
|
@@ -796,7 +800,7 @@ class Allocation(TimestampMixin, ORMBase, OtherModels):
|
|
|
796
800
|
|
|
797
801
|
return query.one()
|
|
798
802
|
|
|
799
|
-
def siblings(self, imaginary: bool = True) ->
|
|
803
|
+
def siblings(self, imaginary: bool = True) -> list[Self]:
|
|
800
804
|
"""Returns the master/mirrors group this allocation is part of.
|
|
801
805
|
|
|
802
806
|
If 'imaginary' is true, inexistant mirrors are created on the fly.
|
libres/db/models/base.py
CHANGED
libres/db/models/other.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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(
|
|
6
|
-
Allocation:
|
|
7
|
-
ReservedSlot:
|
|
8
|
-
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) ->
|
|
24
|
+
def models(self) -> _Models:
|
|
20
25
|
global models
|
|
21
26
|
if not models:
|
|
22
27
|
# FIXME: libres.db exports ORMBase, do we really
|
libres/db/models/reservation.py
CHANGED
|
@@ -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
|
-
|
|
17
|
-
|
|
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(
|
|
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:
|
|
42
|
+
id: Column[int] = Column(
|
|
38
43
|
types.Integer(),
|
|
39
44
|
primary_key=True,
|
|
40
45
|
autoincrement=True
|
|
41
46
|
)
|
|
42
47
|
|
|
43
|
-
token:
|
|
48
|
+
token: Column[uuid.UUID] = Column(
|
|
44
49
|
UUID(),
|
|
45
50
|
nullable=False,
|
|
46
51
|
)
|
|
47
52
|
|
|
48
|
-
target:
|
|
53
|
+
target: Column[uuid.UUID] = Column(
|
|
49
54
|
UUID(),
|
|
50
55
|
nullable=False,
|
|
51
56
|
)
|
|
52
57
|
|
|
53
|
-
target_type:
|
|
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:
|
|
66
|
+
type: Column[str | None] = Column(
|
|
62
67
|
types.Text(),
|
|
63
68
|
nullable=True
|
|
64
69
|
)
|
|
65
70
|
|
|
66
|
-
resource:
|
|
71
|
+
resource: Column[uuid.UUID] = Column(
|
|
67
72
|
UUID(),
|
|
68
73
|
nullable=False
|
|
69
74
|
)
|
|
70
75
|
|
|
71
|
-
start:
|
|
76
|
+
start: Column[datetime | None] = Column(
|
|
72
77
|
UTCDateTime(timezone=False),
|
|
73
78
|
nullable=True
|
|
74
79
|
)
|
|
75
80
|
|
|
76
|
-
end:
|
|
81
|
+
end: Column[datetime | None] = Column(
|
|
77
82
|
UTCDateTime(timezone=False),
|
|
78
83
|
nullable=True
|
|
79
84
|
)
|
|
80
85
|
|
|
81
|
-
timezone:
|
|
86
|
+
timezone: Column[str | None] = Column(
|
|
82
87
|
types.String(),
|
|
83
88
|
nullable=True
|
|
84
89
|
)
|
|
85
90
|
|
|
86
|
-
status:
|
|
91
|
+
status: Column[Literal['pending', 'approved']] = Column(
|
|
87
92
|
types.Enum( # type:ignore[arg-type]
|
|
88
93
|
'pending', 'approved',
|
|
89
|
-
name=
|
|
94
|
+
name='reservation_status'
|
|
90
95
|
),
|
|
91
96
|
nullable=False
|
|
92
97
|
)
|
|
93
98
|
|
|
94
|
-
data:
|
|
99
|
+
data: Column[Any | None] = deferred(
|
|
95
100
|
Column(
|
|
96
101
|
JSON(),
|
|
97
102
|
nullable=True
|
|
98
103
|
)
|
|
99
104
|
)
|
|
100
105
|
|
|
101
|
-
email:
|
|
106
|
+
email: Column[str] = Column(
|
|
102
107
|
types.Unicode(254),
|
|
103
108
|
nullable=False
|
|
104
109
|
)
|
|
105
110
|
|
|
106
|
-
session_id:
|
|
111
|
+
session_id: Column[uuid.UUID | None] = Column(
|
|
107
112
|
UUID()
|
|
108
113
|
)
|
|
109
114
|
|
|
110
|
-
quota:
|
|
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) ->
|
|
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
|
|
|
@@ -147,7 +152,7 @@ class Reservation(TimestampMixin, ORMBase, OtherModels):
|
|
|
147
152
|
|
|
148
153
|
def display_start(
|
|
149
154
|
self,
|
|
150
|
-
timezone:
|
|
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:
|
|
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) ->
|
|
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`
|
|
@@ -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
|
-
|
|
22
|
-
if
|
|
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:
|
|
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:
|
|
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:
|
|
48
|
+
end: Column[datetime] = Column(
|
|
47
49
|
UTCDateTime(timezone=False),
|
|
48
50
|
nullable=False
|
|
49
51
|
)
|
|
50
52
|
|
|
51
|
-
allocation_id:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
96
|
+
timezone: TzInfoOrName | None = None
|
|
95
97
|
) -> datetime:
|
|
96
98
|
|
|
97
99
|
if timezone is None:
|
libres/db/models/timestamp.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import sedate
|
|
2
4
|
|
|
3
5
|
from libres.db.models.types import UTCDateTime
|
|
@@ -6,8 +8,8 @@ from sqlalchemy.orm import deferred
|
|
|
6
8
|
from sqlalchemy.schema import Column
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
if
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
if TYPE_CHECKING:
|
|
11
13
|
from datetime import datetime
|
|
12
14
|
|
|
13
15
|
|
|
@@ -22,16 +24,16 @@ class TimestampMixin:
|
|
|
22
24
|
"""
|
|
23
25
|
|
|
24
26
|
@staticmethod
|
|
25
|
-
def timestamp() ->
|
|
27
|
+
def timestamp() -> datetime:
|
|
26
28
|
return sedate.utcnow()
|
|
27
29
|
|
|
28
|
-
if
|
|
30
|
+
if TYPE_CHECKING:
|
|
29
31
|
created: Column[datetime]
|
|
30
|
-
modified: Column[
|
|
32
|
+
modified: Column[datetime | None]
|
|
31
33
|
|
|
32
34
|
else:
|
|
33
35
|
@declared_attr
|
|
34
|
-
def created(cls) ->
|
|
36
|
+
def created(cls) -> Column[datetime]:
|
|
35
37
|
return deferred(
|
|
36
38
|
Column(
|
|
37
39
|
UTCDateTime(timezone=False),
|
|
@@ -40,7 +42,7 @@ class TimestampMixin:
|
|
|
40
42
|
)
|
|
41
43
|
|
|
42
44
|
@declared_attr
|
|
43
|
-
def modified(cls) ->
|
|
45
|
+
def modified(cls) -> Column[datetime | None]:
|
|
44
46
|
return deferred(
|
|
45
47
|
Column(
|
|
46
48
|
UTCDateTime(timezone=False),
|