dycw-utilities 0.131.14__py3-none-any.whl → 0.131.16__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.
- {dycw_utilities-0.131.14.dist-info → dycw_utilities-0.131.16.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.14.dist-info → dycw_utilities-0.131.16.dist-info}/RECORD +10 -10
- utilities/__init__.py +1 -1
- utilities/click.py +128 -179
- utilities/hypothesis.py +57 -19
- utilities/luigi.py +71 -104
- utilities/whenever.py +1 -192
- utilities/whenever2.py +209 -8
- {dycw_utilities-0.131.14.dist-info → dycw_utilities-0.131.16.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.14.dist-info → dycw_utilities-0.131.16.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,11 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=I-o_EPHIejt3ggoklYx3Vx_Wi2XLiPZ3YlzjyX4JBKI,61
|
2
2
|
utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
|
3
3
|
utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
|
4
4
|
utilities/asyncio.py,sha256=yfKvAIDCRrWdyQMVZMo4DJQx4nVrXoAcqwhNuF95Ryo,38186
|
5
5
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
6
6
|
utilities/atools.py,sha256=-bFGIrwYMFR7xl39j02DZMsO_u5x5_Ph7bRlBUFVYyw,1048
|
7
7
|
utilities/cachetools.py,sha256=uBtEv4hD-TuCPX_cQy1lOpLF-QqfwnYGSf0o4Soqydc,2826
|
8
|
-
utilities/click.py,sha256=
|
8
|
+
utilities/click.py,sha256=sdGAMoarAWJ4_A3o-UzM7hpe7R37yvC_X_Uh8vasa-A,13295
|
9
9
|
utilities/concurrent.py,sha256=s2scTEd2AhXVTW4hpASU2qxV_DiVLALfms55cCQzCvM,2886
|
10
10
|
utilities/contextlib.py,sha256=lpaLJBy3X0UGLWjM98jkQZZq8so4fRmoK-Bheq0uOW4,1027
|
11
11
|
utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
|
@@ -24,7 +24,7 @@ utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
|
24
24
|
utilities/git.py,sha256=oi7-_l5e9haSANSCvQw25ufYGoNahuUPHAZ6114s3JQ,1191
|
25
25
|
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
26
26
|
utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
|
27
|
-
utilities/hypothesis.py,sha256=
|
27
|
+
utilities/hypothesis.py,sha256=fcklSUIagoDZOTn8ITbkIU7BOZMV4g1Owj0khiz9yBY,43401
|
28
28
|
utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
|
29
29
|
utilities/inflect.py,sha256=DbqB5Q9FbRGJ1NbvEiZBirRMxCxgrz91zy5jCO9ZIs0,347
|
30
30
|
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
@@ -33,7 +33,7 @@ utilities/jupyter.py,sha256=ft5JA7fBxXKzP-L9W8f2-wbF0QeYc_2uLQNFDVk4Z-M,2917
|
|
33
33
|
utilities/libcst.py,sha256=Jto5ppzRzsxn4AD32IS8n0lbgLYXwsVJB6EY8giNZyY,4974
|
34
34
|
utilities/lightweight_charts.py,sha256=JrkrAZMo6JID2Eoc9QCc05Y_pK4l2zsApIhmii1z2Ig,2764
|
35
35
|
utilities/logging.py,sha256=zm5k0Cduxtx2H2o7odxUTJtPNkJS85mqHYN1cS5Kc1w,17863
|
36
|
-
utilities/luigi.py,sha256=
|
36
|
+
utilities/luigi.py,sha256=UAt4TDMtinLAN7sipX0jSvH-aZzHUTQbHB3Rwtbq994,4840
|
37
37
|
utilities/math.py,sha256=_6vrDyjtaqE_OFE-F2DNWrDG_J_kMl3nFAJsok9v_bY,26862
|
38
38
|
utilities/memory_profiler.py,sha256=tf2C51P2lCujPGvRt2Rfc7VEw5LDXmVPCG3z_AvBmbU,962
|
39
39
|
utilities/modules.py,sha256=iuvLluJya-hvl1Q25-Jk3dLgx2Es3ck4SjJiEkAlVTs,3195
|
@@ -88,11 +88,11 @@ utilities/tzlocal.py,sha256=xbBBzVIUKMk8AkhuIp1qxGRNBioIa5I09dpeoBnIOOU,662
|
|
88
88
|
utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
|
89
89
|
utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
|
90
90
|
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
91
|
-
utilities/whenever.py,sha256=
|
92
|
-
utilities/whenever2.py,sha256=
|
91
|
+
utilities/whenever.py,sha256=oO0sgIIv4tvhIYmlZ4RFFfY6P54CPeyJCY9A4XHuyqo,11916
|
92
|
+
utilities/whenever2.py,sha256=Jd_fEavXCWTdMj29L7j-HbeMvrl8e3Ah_nxcLEFAwOU,12026
|
93
93
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
94
94
|
utilities/zoneinfo.py,sha256=gJPr9l7V8s3Y7TXpCGYEM1S81Rplb9e4MoV9Nvy2VU8,1852
|
95
|
-
dycw_utilities-0.131.
|
96
|
-
dycw_utilities-0.131.
|
97
|
-
dycw_utilities-0.131.
|
98
|
-
dycw_utilities-0.131.
|
95
|
+
dycw_utilities-0.131.16.dist-info/METADATA,sha256=X0Yt3BUVE7Y7W884VYvx_9CVhE4c9ENSxbhD4ghFoKU,1585
|
96
|
+
dycw_utilities-0.131.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
97
|
+
dycw_utilities-0.131.16.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
98
|
+
dycw_utilities-0.131.16.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/click.py
CHANGED
@@ -1,40 +1,38 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import datetime as dt
|
4
3
|
import pathlib
|
5
|
-
from typing import TYPE_CHECKING, Generic, TypedDict, TypeVar, override
|
6
|
-
from uuid import UUID
|
4
|
+
from typing import TYPE_CHECKING, Generic, TypedDict, TypeVar, assert_never, override
|
7
5
|
|
8
6
|
import click
|
7
|
+
import whenever
|
9
8
|
from click import Choice, Context, Parameter, ParamType
|
10
|
-
from click.types import
|
11
|
-
BoolParamType,
|
12
|
-
FloatParamType,
|
13
|
-
IntParamType,
|
14
|
-
StringParamType,
|
15
|
-
UUIDParameterType,
|
16
|
-
)
|
9
|
+
from click.types import StringParamType
|
17
10
|
|
18
|
-
|
19
|
-
import utilities.types
|
20
|
-
from utilities.datetime import EnsureMonthError, MonthLike, ensure_month
|
11
|
+
from utilities.datetime import EnsureMonthError, ensure_month
|
21
12
|
from utilities.enum import EnsureEnumError, ensure_enum
|
22
13
|
from utilities.functions import EnsureStrError, ensure_str, get_class_name
|
23
14
|
from utilities.iterables import is_iterable_not_str
|
24
15
|
from utilities.text import split_str
|
25
16
|
from utilities.types import (
|
26
|
-
|
17
|
+
DateDeltaLike,
|
18
|
+
DateLike,
|
19
|
+
DateTimeDeltaLike,
|
27
20
|
EnumLike,
|
28
21
|
MaybeStr,
|
29
|
-
|
30
|
-
PyTimeDeltaLike,
|
31
|
-
PyTimeLike,
|
22
|
+
PlainDateTimeLike,
|
32
23
|
TEnum,
|
24
|
+
TimeDeltaLike,
|
25
|
+
TimeLike,
|
26
|
+
ZonedDateTimeLike,
|
33
27
|
)
|
34
28
|
|
35
29
|
if TYPE_CHECKING:
|
36
30
|
from collections.abc import Iterable, Sequence
|
37
31
|
|
32
|
+
import utilities.datetime
|
33
|
+
from utilities.datetime import MonthLike
|
34
|
+
|
35
|
+
|
38
36
|
_T = TypeVar("_T")
|
39
37
|
_TParam = TypeVar("_TParam", bound=ParamType)
|
40
38
|
|
@@ -76,21 +74,25 @@ class Date(ParamType):
|
|
76
74
|
|
77
75
|
@override
|
78
76
|
def convert(
|
79
|
-
self, value:
|
80
|
-
) ->
|
77
|
+
self, value: DateLike, param: Parameter | None, ctx: Context | None
|
78
|
+
) -> whenever.Date:
|
81
79
|
"""Convert a value into the `Date` type."""
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
80
|
+
match value:
|
81
|
+
case whenever.Date():
|
82
|
+
return value
|
83
|
+
case str():
|
84
|
+
try:
|
85
|
+
return whenever.Date.parse_common_iso(value)
|
86
|
+
except ValueError as error:
|
87
|
+
self.fail(str(error), param, ctx)
|
88
|
+
case _ as never:
|
89
|
+
assert_never(never)
|
88
90
|
|
89
91
|
|
90
|
-
class
|
91
|
-
"""A
|
92
|
+
class DateDelta(ParamType):
|
93
|
+
"""A date-delta-valued parameter."""
|
92
94
|
|
93
|
-
name = "
|
95
|
+
name = "date delta"
|
94
96
|
|
95
97
|
@override
|
96
98
|
def __repr__(self) -> str:
|
@@ -98,18 +100,45 @@ class Duration(ParamType):
|
|
98
100
|
|
99
101
|
@override
|
100
102
|
def convert(
|
101
|
-
self,
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
103
|
+
self, value: DateDeltaLike, param: Parameter | None, ctx: Context | None
|
104
|
+
) -> whenever.DateDelta:
|
105
|
+
"""Convert a value into the `DateDelta` type."""
|
106
|
+
match value:
|
107
|
+
case whenever.DateDelta():
|
108
|
+
return value
|
109
|
+
case str():
|
110
|
+
try:
|
111
|
+
return whenever.DateDelta.parse_common_iso(value)
|
112
|
+
except ValueError as error:
|
113
|
+
self.fail(str(error), param, ctx)
|
114
|
+
case _ as never:
|
115
|
+
assert_never(never)
|
116
|
+
|
117
|
+
|
118
|
+
class DateTimeDelta(ParamType):
|
119
|
+
"""A date-delta-valued parameter."""
|
120
|
+
|
121
|
+
name = "date-time delta"
|
108
122
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
123
|
+
@override
|
124
|
+
def __repr__(self) -> str:
|
125
|
+
return self.name.upper()
|
126
|
+
|
127
|
+
@override
|
128
|
+
def convert(
|
129
|
+
self, value: DateTimeDeltaLike, param: Parameter | None, ctx: Context | None
|
130
|
+
) -> whenever.DateTimeDelta:
|
131
|
+
"""Convert a value into the `DateTimeDelta` type."""
|
132
|
+
match value:
|
133
|
+
case whenever.DateTimeDelta():
|
134
|
+
return value
|
135
|
+
case str():
|
136
|
+
try:
|
137
|
+
return whenever.DateTimeDelta.parse_common_iso(value)
|
138
|
+
except ValueError as error:
|
139
|
+
self.fail(str(error), param, ctx)
|
140
|
+
case _ as never:
|
141
|
+
assert_never(never)
|
113
142
|
|
114
143
|
|
115
144
|
class Enum(ParamType, Generic[TEnum]):
|
@@ -167,7 +196,7 @@ class Month(ParamType):
|
|
167
196
|
class PlainDateTime(ParamType):
|
168
197
|
"""A local-datetime-valued parameter."""
|
169
198
|
|
170
|
-
name = "plain
|
199
|
+
name = "plain date-time"
|
171
200
|
|
172
201
|
@override
|
173
202
|
def __repr__(self) -> str:
|
@@ -175,15 +204,19 @@ class PlainDateTime(ParamType):
|
|
175
204
|
|
176
205
|
@override
|
177
206
|
def convert(
|
178
|
-
self, value:
|
179
|
-
) ->
|
180
|
-
"""Convert a value into the `
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
207
|
+
self, value: PlainDateTimeLike, param: Parameter | None, ctx: Context | None
|
208
|
+
) -> whenever.PlainDateTime:
|
209
|
+
"""Convert a value into the `PlainDateTime` type."""
|
210
|
+
match value:
|
211
|
+
case whenever.PlainDateTime():
|
212
|
+
return value
|
213
|
+
case str():
|
214
|
+
try:
|
215
|
+
return whenever.PlainDateTime.parse_common_iso(value)
|
216
|
+
except ValueError as error:
|
217
|
+
self.fail(str(error), param, ctx)
|
218
|
+
case _ as never:
|
219
|
+
assert_never(never)
|
187
220
|
|
188
221
|
|
189
222
|
class Time(ParamType):
|
@@ -197,21 +230,25 @@ class Time(ParamType):
|
|
197
230
|
|
198
231
|
@override
|
199
232
|
def convert(
|
200
|
-
self, value:
|
201
|
-
) ->
|
233
|
+
self, value: TimeLike, param: Parameter | None, ctx: Context | None
|
234
|
+
) -> whenever.Time:
|
202
235
|
"""Convert a value into the `Time` type."""
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
236
|
+
match value:
|
237
|
+
case whenever.Time():
|
238
|
+
return value
|
239
|
+
case str():
|
240
|
+
try:
|
241
|
+
return whenever.Time.parse_common_iso(value)
|
242
|
+
except ValueError as error:
|
243
|
+
self.fail(str(error), param, ctx)
|
244
|
+
case _ as never:
|
245
|
+
assert_never(never)
|
246
|
+
|
247
|
+
|
248
|
+
class TimeDelta(ParamType):
|
212
249
|
"""A timedelta-valued parameter."""
|
213
250
|
|
214
|
-
name = "
|
251
|
+
name = "time-delta"
|
215
252
|
|
216
253
|
@override
|
217
254
|
def __repr__(self) -> str:
|
@@ -219,21 +256,25 @@ class Timedelta(ParamType):
|
|
219
256
|
|
220
257
|
@override
|
221
258
|
def convert(
|
222
|
-
self, value:
|
223
|
-
) ->
|
224
|
-
"""Convert a value into the `
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
259
|
+
self, value: TimeDeltaLike, param: Parameter | None, ctx: Context | None
|
260
|
+
) -> whenever.TimeDelta:
|
261
|
+
"""Convert a value into the `TimeDelta` type."""
|
262
|
+
match value:
|
263
|
+
case whenever.TimeDelta():
|
264
|
+
return value
|
265
|
+
case str():
|
266
|
+
try:
|
267
|
+
return whenever.TimeDelta.parse_common_iso(value)
|
268
|
+
except ValueError as error:
|
269
|
+
self.fail(str(error), param, ctx)
|
270
|
+
case _ as never:
|
271
|
+
assert_never(never)
|
231
272
|
|
232
273
|
|
233
274
|
class ZonedDateTime(ParamType):
|
234
275
|
"""A zoned-datetime-valued parameter."""
|
235
276
|
|
236
|
-
name = "zoned
|
277
|
+
name = "zoned date-time"
|
237
278
|
|
238
279
|
@override
|
239
280
|
def __repr__(self) -> str:
|
@@ -241,15 +282,19 @@ class ZonedDateTime(ParamType):
|
|
241
282
|
|
242
283
|
@override
|
243
284
|
def convert(
|
244
|
-
self, value:
|
245
|
-
) ->
|
246
|
-
"""Convert a value into the `
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
285
|
+
self, value: ZonedDateTimeLike, param: Parameter | None, ctx: Context | None
|
286
|
+
) -> whenever.ZonedDateTime:
|
287
|
+
"""Convert a value into the `ZonedDateTime` type."""
|
288
|
+
match value:
|
289
|
+
case whenever.ZonedDateTime():
|
290
|
+
return value
|
291
|
+
case str():
|
292
|
+
try:
|
293
|
+
return whenever.ZonedDateTime.parse_common_iso(value)
|
294
|
+
except ValueError as error:
|
295
|
+
self.fail(str(error), param, ctx)
|
296
|
+
case _ as never:
|
297
|
+
assert_never(never)
|
253
298
|
|
254
299
|
|
255
300
|
# parameters - frozenset
|
@@ -297,20 +342,6 @@ class FrozenSetParameter(ParamType, Generic[_TParam, _T]):
|
|
297
342
|
return _make_metavar(param, desc)
|
298
343
|
|
299
344
|
|
300
|
-
class FrozenSetBools(FrozenSetParameter[BoolParamType, str]):
|
301
|
-
"""A frozenset-of-bools-valued parameter."""
|
302
|
-
|
303
|
-
def __init__(self, *, separator: str = ",") -> None:
|
304
|
-
super().__init__(BoolParamType(), separator=separator)
|
305
|
-
|
306
|
-
|
307
|
-
class FrozenSetDates(FrozenSetParameter[Date, dt.date]):
|
308
|
-
"""A frozenset-of-dates-valued parameter."""
|
309
|
-
|
310
|
-
def __init__(self, *, separator: str = ",") -> None:
|
311
|
-
super().__init__(Date(), separator=separator)
|
312
|
-
|
313
|
-
|
314
345
|
class FrozenSetChoices(FrozenSetParameter[Choice, str]):
|
315
346
|
"""A frozenset-of-choices-valued parameter."""
|
316
347
|
|
@@ -341,27 +372,6 @@ class FrozenSetEnums(FrozenSetParameter[Enum[TEnum], TEnum]):
|
|
341
372
|
super().__init__(Enum(enum, case_sensitive=case_sensitive), separator=separator)
|
342
373
|
|
343
374
|
|
344
|
-
class FrozenSetFloats(FrozenSetParameter[FloatParamType, float]):
|
345
|
-
"""A frozenset-of-floats-valued parameter."""
|
346
|
-
|
347
|
-
def __init__(self, *, separator: str = ",") -> None:
|
348
|
-
super().__init__(FloatParamType(), separator=separator)
|
349
|
-
|
350
|
-
|
351
|
-
class FrozenSetInts(FrozenSetParameter[IntParamType, int]):
|
352
|
-
"""A frozenset-of-ints-valued parameter."""
|
353
|
-
|
354
|
-
def __init__(self, *, separator: str = ",") -> None:
|
355
|
-
super().__init__(IntParamType(), separator=separator)
|
356
|
-
|
357
|
-
|
358
|
-
class FrozenSetMonths(FrozenSetParameter[Month, utilities.datetime.Month]):
|
359
|
-
"""A frozenset-of-months-valued parameter."""
|
360
|
-
|
361
|
-
def __init__(self, *, separator: str = ",") -> None:
|
362
|
-
super().__init__(Month(), separator=separator)
|
363
|
-
|
364
|
-
|
365
375
|
class FrozenSetStrs(FrozenSetParameter[StringParamType, str]):
|
366
376
|
"""A frozenset-of-strs-valued parameter."""
|
367
377
|
|
@@ -369,13 +379,6 @@ class FrozenSetStrs(FrozenSetParameter[StringParamType, str]):
|
|
369
379
|
super().__init__(StringParamType(), separator=separator)
|
370
380
|
|
371
381
|
|
372
|
-
class FrozenSetUUIDs(FrozenSetParameter[UUIDParameterType, UUID]):
|
373
|
-
"""A frozenset-of-UUIDs-valued parameter."""
|
374
|
-
|
375
|
-
def __init__(self, *, separator: str = ",") -> None:
|
376
|
-
super().__init__(UUIDParameterType(), separator=separator)
|
377
|
-
|
378
|
-
|
379
382
|
# parameters - list
|
380
383
|
|
381
384
|
|
@@ -421,20 +424,6 @@ class ListParameter(ParamType, Generic[_TParam, _T]):
|
|
421
424
|
return _make_metavar(param, desc)
|
422
425
|
|
423
426
|
|
424
|
-
class ListBools(ListParameter[BoolParamType, str]):
|
425
|
-
"""A list-of-bools-valued parameter."""
|
426
|
-
|
427
|
-
def __init__(self, *, separator: str = ",") -> None:
|
428
|
-
super().__init__(BoolParamType(), separator=separator)
|
429
|
-
|
430
|
-
|
431
|
-
class ListDates(ListParameter[Date, dt.date]):
|
432
|
-
"""A list-of-dates-valued parameter."""
|
433
|
-
|
434
|
-
def __init__(self, *, separator: str = ",") -> None:
|
435
|
-
super().__init__(Date(), separator=separator)
|
436
|
-
|
437
|
-
|
438
427
|
class ListEnums(ListParameter[Enum[TEnum], TEnum]):
|
439
428
|
"""A list-of-enums-valued parameter."""
|
440
429
|
|
@@ -449,27 +438,6 @@ class ListEnums(ListParameter[Enum[TEnum], TEnum]):
|
|
449
438
|
super().__init__(Enum(enum, case_sensitive=case_sensitive), separator=separator)
|
450
439
|
|
451
440
|
|
452
|
-
class ListFloats(ListParameter[FloatParamType, float]):
|
453
|
-
"""A list-of-floats-valued parameter."""
|
454
|
-
|
455
|
-
def __init__(self, *, separator: str = ",") -> None:
|
456
|
-
super().__init__(FloatParamType(), separator=separator)
|
457
|
-
|
458
|
-
|
459
|
-
class ListInts(ListParameter[IntParamType, int]):
|
460
|
-
"""A list-of-ints-valued parameter."""
|
461
|
-
|
462
|
-
def __init__(self, *, separator: str = ",") -> None:
|
463
|
-
super().__init__(IntParamType(), separator=separator)
|
464
|
-
|
465
|
-
|
466
|
-
class ListMonths(ListParameter[Month, utilities.datetime.Month]):
|
467
|
-
"""A list-of-months-valued parameter."""
|
468
|
-
|
469
|
-
def __init__(self, *, separator: str = ",") -> None:
|
470
|
-
super().__init__(Month(), separator=separator)
|
471
|
-
|
472
|
-
|
473
441
|
class ListStrs(ListParameter[StringParamType, str]):
|
474
442
|
"""A list-of-strs-valued parameter."""
|
475
443
|
|
@@ -477,13 +445,6 @@ class ListStrs(ListParameter[StringParamType, str]):
|
|
477
445
|
super().__init__(StringParamType(), separator=separator)
|
478
446
|
|
479
447
|
|
480
|
-
class ListUUIDs(ListParameter[UUIDParameterType, UUID]):
|
481
|
-
"""A list-of-UUIDs-valued parameter."""
|
482
|
-
|
483
|
-
def __init__(self, *, separator: str = ",") -> None:
|
484
|
-
super().__init__(UUIDParameterType(), separator=separator)
|
485
|
-
|
486
|
-
|
487
448
|
# private
|
488
449
|
|
489
450
|
|
@@ -495,34 +456,22 @@ def _make_metavar(param: Parameter, desc: str, /) -> str:
|
|
495
456
|
__all__ = [
|
496
457
|
"CONTEXT_SETTINGS_HELP_OPTION_NAMES",
|
497
458
|
"Date",
|
459
|
+
"DateDelta",
|
460
|
+
"DateTimeDelta",
|
498
461
|
"DirPath",
|
499
|
-
"Duration",
|
500
462
|
"Enum",
|
501
463
|
"ExistingDirPath",
|
502
464
|
"ExistingFilePath",
|
503
465
|
"FilePath",
|
504
|
-
"FrozenSetBools",
|
505
466
|
"FrozenSetChoices",
|
506
|
-
"FrozenSetDates",
|
507
467
|
"FrozenSetEnums",
|
508
|
-
"FrozenSetFloats",
|
509
|
-
"FrozenSetInts",
|
510
|
-
"FrozenSetMonths",
|
511
468
|
"FrozenSetParameter",
|
512
469
|
"FrozenSetStrs",
|
513
|
-
"FrozenSetUUIDs",
|
514
|
-
"ListBools",
|
515
|
-
"ListDates",
|
516
470
|
"ListEnums",
|
517
|
-
"ListFloats",
|
518
|
-
"ListInts",
|
519
|
-
"ListMonths",
|
520
471
|
"ListParameter",
|
521
472
|
"ListStrs",
|
522
|
-
"ListUUIDs",
|
523
|
-
"Month",
|
524
473
|
"PlainDateTime",
|
525
474
|
"Time",
|
526
|
-
"
|
475
|
+
"TimeDelta",
|
527
476
|
"ZonedDateTime",
|
528
477
|
]
|
utilities/hypothesis.py
CHANGED
@@ -48,7 +48,15 @@ from hypothesis.strategies import (
|
|
48
48
|
uuids,
|
49
49
|
)
|
50
50
|
from hypothesis.utils.conventions import not_set
|
51
|
-
from whenever import
|
51
|
+
from whenever import (
|
52
|
+
Date,
|
53
|
+
DateDelta,
|
54
|
+
DateTimeDelta,
|
55
|
+
PlainDateTime,
|
56
|
+
Time,
|
57
|
+
TimeDelta,
|
58
|
+
ZonedDateTime,
|
59
|
+
)
|
52
60
|
|
53
61
|
from utilities.datetime import (
|
54
62
|
DATETIME_MAX_NAIVE,
|
@@ -97,12 +105,19 @@ from utilities.whenever2 import (
|
|
97
105
|
DATE_DELTA_PARSABLE_MIN,
|
98
106
|
DATE_MAX,
|
99
107
|
DATE_MIN,
|
108
|
+
DATE_TIME_DELTA_MAX,
|
109
|
+
DATE_TIME_DELTA_MIN,
|
110
|
+
DATE_TIME_DELTA_PARSABLE_MAX,
|
111
|
+
DATE_TIME_DELTA_PARSABLE_MIN,
|
100
112
|
PLAIN_DATE_TIME_MAX,
|
101
113
|
PLAIN_DATE_TIME_MIN,
|
102
114
|
TIME_DELTA_MAX,
|
103
115
|
TIME_DELTA_MIN,
|
104
116
|
TIME_MAX,
|
105
117
|
TIME_MIN,
|
118
|
+
to_date_time_delta,
|
119
|
+
to_days,
|
120
|
+
to_nanos,
|
106
121
|
)
|
107
122
|
from utilities.zoneinfo import UTC, ensure_time_zone
|
108
123
|
|
@@ -199,25 +214,11 @@ def date_deltas_whenever(
|
|
199
214
|
...
|
200
215
|
case _ as never:
|
201
216
|
assert_never(never)
|
202
|
-
|
203
|
-
|
204
|
-
assert min_months == 0
|
205
|
-
max_years, max_months, max_days = max_value_.in_years_months_days()
|
206
|
-
assert max_years == 0
|
207
|
-
assert max_months == 0
|
217
|
+
min_days = to_days(min_value_)
|
218
|
+
max_days = to_days(max_value_)
|
208
219
|
if draw2(draw, parsable):
|
209
|
-
|
210
|
-
|
211
|
-
)
|
212
|
-
assert parsable_min_years == 0
|
213
|
-
assert parsable_min_months == 0
|
214
|
-
min_days = max(min_days, parsable_min_days)
|
215
|
-
parsable_max_years, parsable_max_months, parsable_max_days = (
|
216
|
-
DATE_DELTA_PARSABLE_MAX.in_years_months_days()
|
217
|
-
)
|
218
|
-
assert parsable_max_years == 0
|
219
|
-
assert parsable_max_months == 0
|
220
|
-
max_days = min(max_days, parsable_max_days)
|
220
|
+
min_days = max(min_days, to_days(DATE_DELTA_PARSABLE_MIN))
|
221
|
+
max_days = min(max_days, to_days(DATE_DELTA_PARSABLE_MAX))
|
221
222
|
days = draw(integers(min_value=min_days, max_value=max_days))
|
222
223
|
return DateDelta(days=days)
|
223
224
|
|
@@ -287,6 +288,42 @@ def _is_between_timedelta(
|
|
287
288
|
##
|
288
289
|
|
289
290
|
|
291
|
+
@composite
|
292
|
+
def date_time_deltas_whenever(
|
293
|
+
draw: DrawFn,
|
294
|
+
/,
|
295
|
+
*,
|
296
|
+
min_value: MaybeSearchStrategy[DateTimeDelta | None] = None,
|
297
|
+
max_value: MaybeSearchStrategy[DateTimeDelta | None] = None,
|
298
|
+
parsable: MaybeSearchStrategy[bool] = False,
|
299
|
+
) -> DateTimeDelta:
|
300
|
+
"""Strategy for generating date deltas."""
|
301
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
302
|
+
match min_value_:
|
303
|
+
case None:
|
304
|
+
min_value_ = DATE_TIME_DELTA_MIN
|
305
|
+
case DateTimeDelta():
|
306
|
+
...
|
307
|
+
case _ as never:
|
308
|
+
assert_never(never)
|
309
|
+
match max_value_:
|
310
|
+
case None:
|
311
|
+
max_value_ = DATE_TIME_DELTA_MAX
|
312
|
+
case DateTimeDelta():
|
313
|
+
...
|
314
|
+
case _ as never:
|
315
|
+
assert_never(never)
|
316
|
+
min_nanos, max_nanos = map(to_nanos, [min_value_, max_value_])
|
317
|
+
if draw2(draw, parsable):
|
318
|
+
min_nanos = max(min_nanos, to_nanos(DATE_TIME_DELTA_PARSABLE_MIN))
|
319
|
+
max_nanos = min(max_nanos, to_nanos(DATE_TIME_DELTA_PARSABLE_MAX))
|
320
|
+
nanos = draw(integers(min_value=min_nanos, max_value=max_nanos))
|
321
|
+
return to_date_time_delta(nanos)
|
322
|
+
|
323
|
+
|
324
|
+
##
|
325
|
+
|
326
|
+
|
290
327
|
@composite
|
291
328
|
def dates_two_digit_year(
|
292
329
|
draw: DrawFn,
|
@@ -1468,6 +1505,7 @@ __all__ = [
|
|
1468
1505
|
"bool_arrays",
|
1469
1506
|
"date_deltas_whenever",
|
1470
1507
|
"date_durations",
|
1508
|
+
"date_time_deltas_whenever",
|
1471
1509
|
"dates_two_digit_year",
|
1472
1510
|
"dates_whenever",
|
1473
1511
|
"datetime_durations",
|
utilities/luigi.py
CHANGED
@@ -2,121 +2,91 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
from pathlib import Path
|
5
|
-
from typing import
|
5
|
+
from typing import (
|
6
|
+
TYPE_CHECKING,
|
7
|
+
Any,
|
8
|
+
Literal,
|
9
|
+
TypeVar,
|
10
|
+
assert_never,
|
11
|
+
cast,
|
12
|
+
overload,
|
13
|
+
override,
|
14
|
+
)
|
6
15
|
|
7
16
|
import luigi
|
8
17
|
from luigi import Parameter, PathParameter, Target, Task
|
9
18
|
from luigi import build as _build
|
10
|
-
|
11
|
-
from
|
19
|
+
from luigi.parameter import ParameterVisibility, _no_value
|
20
|
+
from whenever import ZonedDateTime
|
12
21
|
|
13
22
|
if TYPE_CHECKING:
|
14
|
-
import
|
15
|
-
from collections.abc import Iterable
|
23
|
+
from collections.abc import Callable, Iterable
|
16
24
|
|
17
25
|
from luigi.execution_summary import LuigiRunResult
|
18
26
|
|
19
|
-
from utilities.types import LogLevel,
|
20
|
-
|
21
|
-
|
22
|
-
# parameters
|
23
|
-
|
24
|
-
|
25
|
-
class DateHourParameter(luigi.DateHourParameter):
|
26
|
-
"""A parameter which takes the value of an hourly `dt.datetime`."""
|
27
|
-
|
28
|
-
def __init__(self, interval: int = 1, **kwargs: Any) -> None:
|
29
|
-
super().__init__(interval, EPOCH_UTC, **kwargs)
|
30
|
-
|
31
|
-
@override
|
32
|
-
def normalize(self, dt: MaybeStr[dt.datetime]) -> dt.datetime:
|
33
|
-
from utilities.whenever import ensure_zoned_datetime
|
34
|
-
|
35
|
-
return ensure_zoned_datetime(dt)
|
36
|
-
|
37
|
-
@override
|
38
|
-
def parse(self, s: str) -> dt.datetime:
|
39
|
-
from utilities.whenever import parse_zoned_datetime
|
40
|
-
|
41
|
-
return parse_zoned_datetime(s)
|
42
|
-
|
43
|
-
@override
|
44
|
-
def serialize(self, dt: dt.datetime) -> str:
|
45
|
-
from utilities.whenever import serialize_zoned_datetime
|
46
|
-
|
47
|
-
return serialize_zoned_datetime(dt)
|
48
|
-
|
49
|
-
|
50
|
-
class DateMinuteParameter(luigi.DateMinuteParameter):
|
51
|
-
"""A parameter which takes the value of a minutely `dt.datetime`."""
|
52
|
-
|
53
|
-
def __init__(self, interval: int = 1, **kwargs: Any) -> None:
|
54
|
-
super().__init__(interval=interval, start=EPOCH_UTC, **kwargs)
|
55
|
-
|
56
|
-
@override
|
57
|
-
def normalize(self, dt: MaybeStr[dt.datetime]) -> dt.datetime:
|
58
|
-
from utilities.whenever import ensure_zoned_datetime
|
59
|
-
|
60
|
-
return ensure_zoned_datetime(dt)
|
61
|
-
|
62
|
-
@override
|
63
|
-
def parse(self, s: str) -> dt.datetime:
|
64
|
-
from utilities.whenever import parse_zoned_datetime
|
65
|
-
|
66
|
-
return parse_zoned_datetime(s)
|
67
|
-
|
68
|
-
@override
|
69
|
-
def serialize(self, dt: dt.datetime) -> str:
|
70
|
-
from utilities.whenever import serialize_zoned_datetime
|
71
|
-
|
72
|
-
return serialize_zoned_datetime(dt)
|
73
|
-
|
27
|
+
from utilities.types import DateTimeRoundUnit, LogLevel, PathLike, ZonedDateTimeLike
|
74
28
|
|
75
|
-
class DateSecondParameter(luigi.DateSecondParameter):
|
76
|
-
"""A parameter which takes the value of a secondly `dt.datetime`."""
|
77
29
|
|
78
|
-
|
79
|
-
super().__init__(interval, EPOCH_UTC, **kwargs)
|
30
|
+
_T = TypeVar("_T")
|
80
31
|
|
81
|
-
@override
|
82
|
-
def normalize(self, dt: MaybeStr[dt.datetime]) -> dt.datetime:
|
83
|
-
from utilities.whenever import ensure_zoned_datetime
|
84
|
-
|
85
|
-
return ensure_zoned_datetime(dt)
|
86
|
-
|
87
|
-
@override
|
88
|
-
def parse(self, s: str) -> dt.datetime:
|
89
|
-
from utilities.whenever import parse_zoned_datetime
|
90
|
-
|
91
|
-
return parse_zoned_datetime(s)
|
92
32
|
|
93
|
-
|
94
|
-
def serialize(self, dt: dt.datetime) -> str:
|
95
|
-
from utilities.whenever import serialize_zoned_datetime
|
96
|
-
|
97
|
-
return serialize_zoned_datetime(dt)
|
98
|
-
|
99
|
-
|
100
|
-
class TimeParameter(Parameter):
|
101
|
-
"""A parameter which takes the value of a `dt.time`."""
|
102
|
-
|
103
|
-
@override
|
104
|
-
def normalize(self, x: MaybeStr[dt.time]) -> dt.time:
|
105
|
-
from utilities.whenever import ensure_time
|
106
|
-
|
107
|
-
return ensure_time(x)
|
108
|
-
|
109
|
-
@override
|
110
|
-
def parse(self, x: str) -> dt.time:
|
111
|
-
from utilities.whenever import parse_time
|
112
|
-
|
113
|
-
return parse_time(x)
|
33
|
+
# parameters
|
114
34
|
|
115
|
-
@override
|
116
|
-
def serialize(self, x: dt.time) -> str:
|
117
|
-
from utilities.whenever import serialize_time
|
118
35
|
|
119
|
-
|
36
|
+
class ZonedDateTimeParameter(Parameter):
|
37
|
+
"""A parameter which takes the value of a zoned datetime."""
|
38
|
+
|
39
|
+
_unit: DateTimeRoundUnit
|
40
|
+
_increment: int
|
41
|
+
|
42
|
+
@override
|
43
|
+
def __init__(
|
44
|
+
self,
|
45
|
+
default: Any = _no_value,
|
46
|
+
is_global: bool = False,
|
47
|
+
significant: bool = True,
|
48
|
+
description: str | None = None,
|
49
|
+
config_path: None = None,
|
50
|
+
positional: bool = True,
|
51
|
+
always_in_help: bool = False,
|
52
|
+
batch_method: Callable[[Iterable[_T]], _T] | None = None,
|
53
|
+
visibility: ParameterVisibility = ParameterVisibility.PUBLIC,
|
54
|
+
*,
|
55
|
+
unit: DateTimeRoundUnit = "second",
|
56
|
+
increment: int = 1,
|
57
|
+
) -> None:
|
58
|
+
super().__init__(
|
59
|
+
default,
|
60
|
+
is_global,
|
61
|
+
significant,
|
62
|
+
description,
|
63
|
+
config_path,
|
64
|
+
positional,
|
65
|
+
always_in_help,
|
66
|
+
batch_method,
|
67
|
+
visibility,
|
68
|
+
)
|
69
|
+
self._unit = unit
|
70
|
+
self._increment = increment
|
71
|
+
|
72
|
+
@override
|
73
|
+
def normalize(self, x: ZonedDateTimeLike) -> ZonedDateTime:
|
74
|
+
match x:
|
75
|
+
case ZonedDateTime() as date_time:
|
76
|
+
...
|
77
|
+
case str() as text:
|
78
|
+
date_time = ZonedDateTime.parse_common_iso(text)
|
79
|
+
case _ as never:
|
80
|
+
assert_never(never)
|
81
|
+
return date_time.round(self._unit, increment=self._increment, mode="floor")
|
82
|
+
|
83
|
+
@override
|
84
|
+
def parse(self, x: str) -> ZonedDateTime:
|
85
|
+
return ZonedDateTime.parse_common_iso(x)
|
86
|
+
|
87
|
+
@override
|
88
|
+
def serialize(self, x: ZonedDateTime) -> str:
|
89
|
+
return x.format_common_iso()
|
120
90
|
|
121
91
|
|
122
92
|
# targets
|
@@ -217,12 +187,9 @@ def build(
|
|
217
187
|
|
218
188
|
|
219
189
|
__all__ = [
|
220
|
-
"DateHourParameter",
|
221
|
-
"DateMinuteParameter",
|
222
|
-
"DateSecondParameter",
|
223
190
|
"ExternalFile",
|
224
191
|
"ExternalTask",
|
225
192
|
"PathTarget",
|
226
|
-
"
|
193
|
+
"ZonedDateTimeParameter",
|
227
194
|
"build",
|
228
195
|
]
|
utilities/whenever.py
CHANGED
@@ -33,14 +33,7 @@ from utilities.re import (
|
|
33
33
|
from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
|
34
34
|
|
35
35
|
if TYPE_CHECKING:
|
36
|
-
from utilities.types import
|
37
|
-
DateTimeLike,
|
38
|
-
Duration,
|
39
|
-
DurationLike,
|
40
|
-
PyDateLike,
|
41
|
-
PyTimeDeltaLike,
|
42
|
-
PyTimeLike,
|
43
|
-
)
|
36
|
+
from utilities.types import Duration
|
44
37
|
|
45
38
|
|
46
39
|
MAX_SERIALIZABLE_TIMEDELTA = dt.timedelta(days=3652060, microseconds=-1)
|
@@ -91,177 +84,6 @@ class _CheckValidZonedDateTimeUnequalError(CheckValidZonedDateTimeError):
|
|
91
84
|
##
|
92
85
|
|
93
86
|
|
94
|
-
def ensure_date(date: PyDateLike, /) -> dt.date:
|
95
|
-
"""Ensure the object is a date."""
|
96
|
-
if isinstance(date, dt.date):
|
97
|
-
check_date_not_datetime(date)
|
98
|
-
return date
|
99
|
-
try:
|
100
|
-
return parse_date(date)
|
101
|
-
except ParseDateError as error:
|
102
|
-
raise EnsureDateError(date=error.date) from None
|
103
|
-
|
104
|
-
|
105
|
-
@dataclass(kw_only=True, slots=True)
|
106
|
-
class EnsureDateError(Exception):
|
107
|
-
date: str
|
108
|
-
|
109
|
-
@override
|
110
|
-
def __str__(self) -> str:
|
111
|
-
return f"Unable to ensure date; got {self.date!r}"
|
112
|
-
|
113
|
-
|
114
|
-
##
|
115
|
-
|
116
|
-
|
117
|
-
def ensure_datetime(datetime: DateTimeLike, /) -> dt.datetime:
|
118
|
-
"""Ensure the object is a datetime."""
|
119
|
-
if isinstance(datetime, dt.datetime):
|
120
|
-
return datetime # skipif-ci-and-windows
|
121
|
-
try:
|
122
|
-
return parse_datetime(datetime)
|
123
|
-
except ParseDateTimeError as error:
|
124
|
-
raise EnsureDateTimeError(datetime=error.datetime) from None
|
125
|
-
|
126
|
-
|
127
|
-
@dataclass(kw_only=True, slots=True)
|
128
|
-
class EnsureDateTimeError(Exception):
|
129
|
-
datetime: str
|
130
|
-
|
131
|
-
@override
|
132
|
-
def __str__(self) -> str:
|
133
|
-
return f"Unable to ensure datetime; got {self.datetime!r}"
|
134
|
-
|
135
|
-
|
136
|
-
##
|
137
|
-
|
138
|
-
|
139
|
-
def ensure_duration(duration: DurationLike, /) -> Duration:
|
140
|
-
"""Ensure the object is a Duration."""
|
141
|
-
if isinstance(duration, int | float | dt.timedelta):
|
142
|
-
return duration
|
143
|
-
try:
|
144
|
-
return parse_duration(duration)
|
145
|
-
except ParseDurationError as error:
|
146
|
-
raise EnsureDurationError(duration=error.duration) from None
|
147
|
-
|
148
|
-
|
149
|
-
@dataclass(kw_only=True, slots=True)
|
150
|
-
class EnsureDurationError(Exception):
|
151
|
-
duration: str
|
152
|
-
|
153
|
-
@override
|
154
|
-
def __str__(self) -> str:
|
155
|
-
return f"Unable to ensure duration; got {self.duration!r}"
|
156
|
-
|
157
|
-
|
158
|
-
##
|
159
|
-
|
160
|
-
|
161
|
-
def ensure_plain_datetime(datetime: DateTimeLike, /) -> dt.datetime:
|
162
|
-
"""Ensure the object is a plain datetime."""
|
163
|
-
if isinstance(datetime, dt.datetime):
|
164
|
-
return datetime
|
165
|
-
try:
|
166
|
-
return parse_plain_datetime(datetime)
|
167
|
-
except ParsePlainDateTimeError as error:
|
168
|
-
raise EnsurePlainDateTimeError(datetime=error.datetime) from None
|
169
|
-
|
170
|
-
|
171
|
-
@dataclass(kw_only=True, slots=True)
|
172
|
-
class EnsurePlainDateTimeError(Exception):
|
173
|
-
datetime: str
|
174
|
-
|
175
|
-
@override
|
176
|
-
def __str__(self) -> str:
|
177
|
-
return f"Unable to ensure plain datetime; got {self.datetime!r}"
|
178
|
-
|
179
|
-
|
180
|
-
##
|
181
|
-
|
182
|
-
|
183
|
-
def ensure_time(time: PyTimeLike, /) -> dt.time:
|
184
|
-
"""Ensure the object is a time."""
|
185
|
-
if isinstance(time, dt.time):
|
186
|
-
return time
|
187
|
-
try:
|
188
|
-
return parse_time(time)
|
189
|
-
except ParseTimeError as error:
|
190
|
-
raise EnsureTimeError(time=error.time) from None
|
191
|
-
|
192
|
-
|
193
|
-
@dataclass(kw_only=True, slots=True)
|
194
|
-
class EnsureTimeError(Exception):
|
195
|
-
time: str
|
196
|
-
|
197
|
-
@override
|
198
|
-
def __str__(self) -> str:
|
199
|
-
return f"Unable to ensure time; got {self.time!r}"
|
200
|
-
|
201
|
-
|
202
|
-
##
|
203
|
-
|
204
|
-
|
205
|
-
def ensure_timedelta(timedelta: PyTimeDeltaLike, /) -> dt.timedelta:
|
206
|
-
"""Ensure the object is a timedelta."""
|
207
|
-
if isinstance(timedelta, dt.timedelta):
|
208
|
-
return timedelta
|
209
|
-
try:
|
210
|
-
return parse_timedelta(timedelta)
|
211
|
-
except _ParseTimedeltaParseError as error:
|
212
|
-
raise _EnsureTimedeltaParseError(timedelta=error.timedelta) from None
|
213
|
-
except _ParseTimedeltaNanosecondError as error:
|
214
|
-
raise _EnsureTimedeltaNanosecondError(
|
215
|
-
timedelta=error.timedelta, nanoseconds=error.nanoseconds
|
216
|
-
) from None
|
217
|
-
|
218
|
-
|
219
|
-
@dataclass(kw_only=True, slots=True)
|
220
|
-
class EnsureTimedeltaError(Exception):
|
221
|
-
timedelta: str
|
222
|
-
|
223
|
-
|
224
|
-
@dataclass(kw_only=True, slots=True)
|
225
|
-
class _EnsureTimedeltaParseError(EnsureTimedeltaError):
|
226
|
-
@override
|
227
|
-
def __str__(self) -> str:
|
228
|
-
return f"Unable to ensure timedelta; got {self.timedelta!r}"
|
229
|
-
|
230
|
-
|
231
|
-
@dataclass(kw_only=True, slots=True)
|
232
|
-
class _EnsureTimedeltaNanosecondError(EnsureTimedeltaError):
|
233
|
-
nanoseconds: int
|
234
|
-
|
235
|
-
@override
|
236
|
-
def __str__(self) -> str:
|
237
|
-
return f"Unable to ensure timedelta; got {self.nanoseconds} nanoseconds"
|
238
|
-
|
239
|
-
|
240
|
-
##
|
241
|
-
|
242
|
-
|
243
|
-
def ensure_zoned_datetime(datetime: DateTimeLike, /) -> dt.datetime:
|
244
|
-
"""Ensure the object is a zoned datetime."""
|
245
|
-
if isinstance(datetime, dt.datetime):
|
246
|
-
return datetime
|
247
|
-
try:
|
248
|
-
return parse_zoned_datetime(datetime)
|
249
|
-
except ParseZonedDateTimeError as error:
|
250
|
-
raise EnsureZonedDateTimeError(datetime=error.datetime) from None
|
251
|
-
|
252
|
-
|
253
|
-
@dataclass(kw_only=True, slots=True)
|
254
|
-
class EnsureZonedDateTimeError(Exception):
|
255
|
-
datetime: str
|
256
|
-
|
257
|
-
@override
|
258
|
-
def __str__(self) -> str:
|
259
|
-
return f"Unable to ensure zoned datetime; got {self.datetime!r}"
|
260
|
-
|
261
|
-
|
262
|
-
##
|
263
|
-
|
264
|
-
|
265
87
|
_PARSE_DATE_YYMMDD_REGEX = re.compile(r"^(\d{2})(\d{2})(\d{2})$")
|
266
88
|
|
267
89
|
|
@@ -593,12 +415,6 @@ __all__ = [
|
|
593
415
|
"MAX_SERIALIZABLE_TIMEDELTA",
|
594
416
|
"MIN_SERIALIZABLE_TIMEDELTA",
|
595
417
|
"CheckValidZonedDateTimeError",
|
596
|
-
"EnsureDateError",
|
597
|
-
"EnsureDateTimeError",
|
598
|
-
"EnsurePlainDateTimeError",
|
599
|
-
"EnsureTimeError",
|
600
|
-
"EnsureTimedeltaError",
|
601
|
-
"EnsureZonedDateTimeError",
|
602
418
|
"ParseDateError",
|
603
419
|
"ParseDateTimeError",
|
604
420
|
"ParseDurationError",
|
@@ -611,13 +427,6 @@ __all__ = [
|
|
611
427
|
"SerializeTimeDeltaError",
|
612
428
|
"SerializeZonedDateTimeError",
|
613
429
|
"check_valid_zoned_datetime",
|
614
|
-
"ensure_date",
|
615
|
-
"ensure_datetime",
|
616
|
-
"ensure_duration",
|
617
|
-
"ensure_plain_datetime",
|
618
|
-
"ensure_time",
|
619
|
-
"ensure_timedelta",
|
620
|
-
"ensure_zoned_datetime",
|
621
430
|
"parse_date",
|
622
431
|
"parse_datetime",
|
623
432
|
"parse_duration",
|
utilities/whenever2.py
CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import datetime as dt
|
4
4
|
from collections.abc import Callable
|
5
|
+
from dataclasses import dataclass
|
5
6
|
from functools import cache
|
6
7
|
from logging import LogRecord
|
7
8
|
from typing import TYPE_CHECKING, Any, assert_never, overload, override
|
@@ -17,6 +18,7 @@ from whenever import (
|
|
17
18
|
)
|
18
19
|
|
19
20
|
from utilities.datetime import maybe_sub_pct_y
|
21
|
+
from utilities.math import sign
|
20
22
|
from utilities.sentinel import Sentinel, sentinel
|
21
23
|
from utilities.tzlocal import LOCAL_TIME_ZONE, LOCAL_TIME_ZONE_NAME
|
22
24
|
from utilities.zoneinfo import UTC, get_time_zone_name
|
@@ -44,16 +46,52 @@ PLAIN_DATE_TIME_MIN = PlainDateTime.from_py_datetime(dt.datetime.min) # noqa: D
|
|
44
46
|
PLAIN_DATE_TIME_MAX = PlainDateTime.from_py_datetime(dt.datetime.max) # noqa: DTZ901
|
45
47
|
ZONED_DATE_TIME_MIN = PLAIN_DATE_TIME_MIN.assume_tz(UTC.key)
|
46
48
|
ZONED_DATE_TIME_MAX = PLAIN_DATE_TIME_MAX.assume_tz(UTC.key)
|
47
|
-
DATE_TIME_DELTA_MIN = DateTimeDelta(days=-3652059, seconds=-316192377600)
|
48
|
-
DATE_TIME_DELTA_MAX = DateTimeDelta(days=3652059, seconds=316192377600)
|
49
|
-
DATE_DELTA_MIN = DATE_TIME_DELTA_MIN.date_part()
|
50
|
-
DATE_DELTA_MAX = DATE_TIME_DELTA_MAX.date_part()
|
51
|
-
TIME_DELTA_MIN = DATE_TIME_DELTA_MIN.time_part()
|
52
|
-
TIME_DELTA_MAX = DATE_TIME_DELTA_MAX.time_part()
|
53
49
|
|
54
50
|
|
55
|
-
|
56
|
-
|
51
|
+
DATE_TIME_DELTA_MIN = DateTimeDelta(
|
52
|
+
weeks=-521722,
|
53
|
+
days=-5,
|
54
|
+
hours=-23,
|
55
|
+
minutes=-59,
|
56
|
+
seconds=-59,
|
57
|
+
milliseconds=-999,
|
58
|
+
microseconds=-999,
|
59
|
+
nanoseconds=-999,
|
60
|
+
)
|
61
|
+
DATE_TIME_DELTA_MAX = DateTimeDelta(
|
62
|
+
weeks=521722,
|
63
|
+
days=5,
|
64
|
+
hours=23,
|
65
|
+
minutes=59,
|
66
|
+
seconds=59,
|
67
|
+
milliseconds=999,
|
68
|
+
microseconds=999,
|
69
|
+
nanoseconds=999,
|
70
|
+
)
|
71
|
+
DATE_DELTA_MIN = DATE_TIME_DELTA_MIN.date_part()
|
72
|
+
DATE_DELTA_MAX = DATE_TIME_DELTA_MAX.date_part()
|
73
|
+
TIME_DELTA_MIN = TimeDelta(hours=-87831216)
|
74
|
+
TIME_DELTA_MAX = TimeDelta(hours=87831216)
|
75
|
+
|
76
|
+
|
77
|
+
DATE_TIME_DELTA_PARSABLE_MIN = DateTimeDelta(
|
78
|
+
weeks=-142857,
|
79
|
+
hours=-23,
|
80
|
+
minutes=-59,
|
81
|
+
seconds=-59,
|
82
|
+
milliseconds=-999,
|
83
|
+
microseconds=-999,
|
84
|
+
nanoseconds=-999,
|
85
|
+
)
|
86
|
+
DATE_TIME_DELTA_PARSABLE_MAX = DateTimeDelta(
|
87
|
+
weeks=142857,
|
88
|
+
hours=23,
|
89
|
+
minutes=59,
|
90
|
+
seconds=59,
|
91
|
+
milliseconds=999,
|
92
|
+
microseconds=999,
|
93
|
+
nanoseconds=999,
|
94
|
+
)
|
57
95
|
DATE_DELTA_PARSABLE_MIN = DateDelta(days=-999999)
|
58
96
|
DATE_DELTA_PARSABLE_MAX = DateDelta(days=999999)
|
59
97
|
|
@@ -163,6 +201,164 @@ def to_date(
|
|
163
201
|
assert_never(never)
|
164
202
|
|
165
203
|
|
204
|
+
##
|
205
|
+
|
206
|
+
|
207
|
+
def to_days(delta: DateDelta, /) -> int:
|
208
|
+
"""Compute the number of days in a date delta."""
|
209
|
+
months, days = delta.in_months_days()
|
210
|
+
if months != 0:
|
211
|
+
raise ToDaysError(months=months)
|
212
|
+
return days
|
213
|
+
|
214
|
+
|
215
|
+
@dataclass(kw_only=True, slots=True)
|
216
|
+
class ToDaysError(Exception):
|
217
|
+
months: int
|
218
|
+
|
219
|
+
@override
|
220
|
+
def __str__(self) -> str:
|
221
|
+
return f"Date delta must not contain months; got {self.months}"
|
222
|
+
|
223
|
+
|
224
|
+
##
|
225
|
+
|
226
|
+
|
227
|
+
def to_date_time_delta(nanos: int, /) -> DateTimeDelta:
|
228
|
+
"""Construct a date-time delta."""
|
229
|
+
components = _to_time_delta_components(nanos)
|
230
|
+
days, hours = divmod(components.hours, 24)
|
231
|
+
weeks, days = divmod(days, 7)
|
232
|
+
match sign(nanos): # pragma: no cover
|
233
|
+
case 1:
|
234
|
+
if hours < 0:
|
235
|
+
hours += 24
|
236
|
+
days -= 1
|
237
|
+
if days < 0:
|
238
|
+
days += 7
|
239
|
+
weeks -= 1
|
240
|
+
case -1:
|
241
|
+
if hours > 0:
|
242
|
+
hours -= 24
|
243
|
+
days += 1
|
244
|
+
if days > 0:
|
245
|
+
days -= 7
|
246
|
+
weeks += 1
|
247
|
+
case 0:
|
248
|
+
...
|
249
|
+
return DateTimeDelta(
|
250
|
+
weeks=weeks,
|
251
|
+
days=days,
|
252
|
+
hours=hours,
|
253
|
+
minutes=components.minutes,
|
254
|
+
seconds=components.seconds,
|
255
|
+
microseconds=components.microseconds,
|
256
|
+
milliseconds=components.milliseconds,
|
257
|
+
nanoseconds=components.nanoseconds,
|
258
|
+
)
|
259
|
+
|
260
|
+
|
261
|
+
##
|
262
|
+
|
263
|
+
|
264
|
+
def to_nanos(delta: DateTimeDelta, /) -> int:
|
265
|
+
"""Compute the number of nanoseconds in a date-time delta."""
|
266
|
+
months, days, _, _ = delta.in_months_days_secs_nanos()
|
267
|
+
if months != 0:
|
268
|
+
raise ToNanosError(months=months)
|
269
|
+
return 24 * 60 * 60 * int(1e9) * days + delta.time_part().in_nanoseconds()
|
270
|
+
|
271
|
+
|
272
|
+
@dataclass(kw_only=True, slots=True)
|
273
|
+
class ToNanosError(Exception):
|
274
|
+
months: int
|
275
|
+
|
276
|
+
@override
|
277
|
+
def __str__(self) -> str:
|
278
|
+
return f"Date-time delta must not contain months; got {self.months}"
|
279
|
+
|
280
|
+
|
281
|
+
##
|
282
|
+
|
283
|
+
|
284
|
+
def to_time_delta(nanos: int, /) -> TimeDelta:
|
285
|
+
"""Construct a time delta."""
|
286
|
+
components = _to_time_delta_components(nanos)
|
287
|
+
return TimeDelta(
|
288
|
+
hours=components.hours,
|
289
|
+
minutes=components.minutes,
|
290
|
+
seconds=components.seconds,
|
291
|
+
microseconds=components.microseconds,
|
292
|
+
milliseconds=components.milliseconds,
|
293
|
+
nanoseconds=components.nanoseconds,
|
294
|
+
)
|
295
|
+
|
296
|
+
|
297
|
+
@dataclass(kw_only=True, slots=True)
|
298
|
+
class _TimeDeltaComponents:
|
299
|
+
hours: int
|
300
|
+
minutes: int
|
301
|
+
seconds: int
|
302
|
+
microseconds: int
|
303
|
+
milliseconds: int
|
304
|
+
nanoseconds: int
|
305
|
+
|
306
|
+
|
307
|
+
def _to_time_delta_components(nanos: int, /) -> _TimeDeltaComponents:
|
308
|
+
sign_use = sign(nanos)
|
309
|
+
micros, nanos = divmod(nanos, int(1e3))
|
310
|
+
millis, micros = divmod(micros, int(1e3))
|
311
|
+
secs, millis = divmod(millis, int(1e3))
|
312
|
+
mins, secs = divmod(secs, 60)
|
313
|
+
hours, mins = divmod(mins, 60)
|
314
|
+
match sign_use: # pragma: no cover
|
315
|
+
case 1:
|
316
|
+
if nanos < 0:
|
317
|
+
nanos += int(1e3)
|
318
|
+
micros -= 1
|
319
|
+
if micros < 0:
|
320
|
+
micros += int(1e3)
|
321
|
+
millis -= 1
|
322
|
+
if millis < 0:
|
323
|
+
millis += int(1e3)
|
324
|
+
secs -= 1
|
325
|
+
if secs < 0:
|
326
|
+
secs += 60
|
327
|
+
mins -= 1
|
328
|
+
if mins < 0:
|
329
|
+
mins += 60
|
330
|
+
hours -= 1
|
331
|
+
case -1:
|
332
|
+
if nanos > 0:
|
333
|
+
nanos -= int(1e3)
|
334
|
+
micros += 1
|
335
|
+
if micros > 0:
|
336
|
+
micros -= int(1e3)
|
337
|
+
millis += 1
|
338
|
+
if millis > 0:
|
339
|
+
millis -= int(1e3)
|
340
|
+
secs += 1
|
341
|
+
if secs > 0:
|
342
|
+
secs -= 60
|
343
|
+
mins += 1
|
344
|
+
if mins > 0:
|
345
|
+
mins -= 60
|
346
|
+
hours += 1
|
347
|
+
case 0:
|
348
|
+
...
|
349
|
+
return _TimeDeltaComponents(
|
350
|
+
hours=hours,
|
351
|
+
minutes=mins,
|
352
|
+
seconds=secs,
|
353
|
+
microseconds=micros,
|
354
|
+
milliseconds=millis,
|
355
|
+
nanoseconds=nanos,
|
356
|
+
)
|
357
|
+
|
358
|
+
|
359
|
+
##
|
360
|
+
|
361
|
+
|
166
362
|
@overload
|
167
363
|
def to_zoned_date_time(*, date_time: MaybeCallableZonedDateTime) -> ZonedDateTime: ...
|
168
364
|
@overload
|
@@ -265,6 +461,8 @@ __all__ = [
|
|
265
461
|
"ZERO_TIME",
|
266
462
|
"ZONED_DATE_TIME_MAX",
|
267
463
|
"ZONED_DATE_TIME_MIN",
|
464
|
+
"ToDaysError",
|
465
|
+
"ToNanosError",
|
268
466
|
"WheneverLogRecord",
|
269
467
|
"format_compact",
|
270
468
|
"format_compact",
|
@@ -276,5 +474,8 @@ __all__ = [
|
|
276
474
|
"get_today",
|
277
475
|
"get_today_local",
|
278
476
|
"to_date",
|
477
|
+
"to_date_time_delta",
|
478
|
+
"to_days",
|
479
|
+
"to_nanos",
|
279
480
|
"to_zoned_date_time",
|
280
481
|
]
|
File without changes
|
File without changes
|