dycw-utilities 0.137.0__py3-none-any.whl → 0.138.1__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.137.0.dist-info → dycw_utilities-0.138.1.dist-info}/METADATA +1 -1
- {dycw_utilities-0.137.0.dist-info → dycw_utilities-0.138.1.dist-info}/RECORD +12 -12
- utilities/__init__.py +1 -1
- utilities/click.py +47 -12
- utilities/hypothesis.py +56 -30
- utilities/orjson.py +17 -1
- utilities/parse.py +6 -3
- utilities/typed_settings.py +5 -0
- utilities/types.py +6 -0
- utilities/whenever.py +18 -142
- {dycw_utilities-0.137.0.dist-info → dycw_utilities-0.138.1.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.137.0.dist-info → dycw_utilities-0.138.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,11 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=Bd8dTrqjGxNQ6TwkIs-2waHukZTqr0rukeUuyCgkrj4,60
|
2
2
|
utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
|
3
3
|
utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
|
4
4
|
utilities/asyncio.py,sha256=dcGeKQzjLBXxKzZkVIk5oZsFXEcynVbRB9iNB5XEDZk,38526
|
5
5
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
6
6
|
utilities/atools.py,sha256=9im2g8OCf-Iynqa8bAv8N0Ycj9QvrJmGO7yLCZEdgII,986
|
7
7
|
utilities/cachetools.py,sha256=v1-9sXHLdOLiwmkq6NB0OUbxeKBuVVN6wmAWefWoaHI,2744
|
8
|
-
utilities/click.py,sha256=
|
8
|
+
utilities/click.py,sha256=PToheYTcKmBwKOMHasZ36CKcMNmM005nKdvOCFUo5dw,16989
|
9
9
|
utilities/concurrent.py,sha256=ZdhcNeBl1-HaAPY3h7bZ5ccuYdfdq2ATHplvZdnzlhk,2858
|
10
10
|
utilities/contextlib.py,sha256=lpaLJBy3X0UGLWjM98jkQZZq8so4fRmoK-Bheq0uOW4,1027
|
11
11
|
utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
|
@@ -23,7 +23,7 @@ utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
|
23
23
|
utilities/git.py,sha256=oi7-_l5e9haSANSCvQw25ufYGoNahuUPHAZ6114s3JQ,1191
|
24
24
|
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
25
25
|
utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
|
26
|
-
utilities/hypothesis.py,sha256=
|
26
|
+
utilities/hypothesis.py,sha256=2VJy9Pxqjy4_ldAwZAR6XEGuG2ZmAN1ycpA_VIGnKew,38819
|
27
27
|
utilities/importlib.py,sha256=mV1xT_O_zt_GnZZ36tl3xOmMaN_3jErDWY54fX39F6Y,429
|
28
28
|
utilities/inflect.py,sha256=DbqB5Q9FbRGJ1NbvEiZBirRMxCxgrz91zy5jCO9ZIs0,347
|
29
29
|
utilities/ipython.py,sha256=V2oMYHvEKvlNBzxDXdLvKi48oUq2SclRg5xasjaXStw,763
|
@@ -40,9 +40,9 @@ utilities/more_itertools.py,sha256=NEGXDT8COaxziaJFTiXYKX0gffwfKhCAMrq-Kgh0w8c,1
|
|
40
40
|
utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
|
41
41
|
utilities/operator.py,sha256=fZM2DBZdjtU8Lh9Z-eHC4ktNr0vO3JfX8hXgIYwdvBI,3826
|
42
42
|
utilities/optuna.py,sha256=C-fhWYiXHVPo1l8QctYkFJ4DyhbSrGorzP1dJb_qvd8,1933
|
43
|
-
utilities/orjson.py,sha256=
|
43
|
+
utilities/orjson.py,sha256=WWV2QukCIuwT8OAOtmKhLhxezXPVbeA_fQCucmGmbRA,37106
|
44
44
|
utilities/os.py,sha256=yMNAKMyY8oFgQ1yN3TQYnwa5-A_FXz4tCDbhIctQHSs,3736
|
45
|
-
utilities/parse.py,sha256=
|
45
|
+
utilities/parse.py,sha256=JcJn5yXKhIWXBCwgBdPsyu7Hvcuw6kyEdqvaebCaI9k,17951
|
46
46
|
utilities/pathlib.py,sha256=jCFPZm4rBKylEva9wDVTwQlTTVKMepu92WrTpoGa438,3248
|
47
47
|
utilities/period.py,sha256=6jEff_qAiE7xdFaQ1DnKgNf10D2wHhzt7hQXCBoKlgc,6842
|
48
48
|
utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
|
@@ -77,18 +77,18 @@ utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
|
|
77
77
|
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
78
78
|
utilities/timer.py,sha256=oXfTii6ymu57niP0BDGZjFD55LEHi2a19kqZKiTgaFQ,2588
|
79
79
|
utilities/traceback.py,sha256=h9yt4C2XkGHXW9e9bz93IGluhIR_Qil8zu5dhC8DXY0,8884
|
80
|
-
utilities/typed_settings.py,sha256=
|
81
|
-
utilities/types.py,sha256
|
80
|
+
utilities/typed_settings.py,sha256=C2i2VK62us_Z5jjca9NLmDFiT3_mkoSCwgU6FDtDhMI,4501
|
81
|
+
utilities/types.py,sha256=-_pXQvmpJhTgEbI13N_zZWINdt0ODBpZ3WdJQUML9GA,17397
|
82
82
|
utilities/typing.py,sha256=Z-_XDaWyT_6wIo3qfNK-hvRlzxP2Jxa9PgXzm5rDYRA,13790
|
83
83
|
utilities/tzdata.py,sha256=fgNVj66yUbCSI_-vrRVzSD3gtf-L_8IEJEPjP_Jel5Y,266
|
84
84
|
utilities/tzlocal.py,sha256=KyCXEgCTjqGFx-389JdTuhMRUaT06U1RCMdWoED-qro,728
|
85
85
|
utilities/uuid.py,sha256=32p7DGHGM2Btx6PcBvCZvERSWbpupMXqx6FppPoSoTU,612
|
86
86
|
utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
|
87
87
|
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
88
|
-
utilities/whenever.py,sha256=
|
88
|
+
utilities/whenever.py,sha256=R5d9UCNCdAOyjwLUmfH2Vn8Ykee8OHQi2skRTFfbZMM,20492
|
89
89
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
90
90
|
utilities/zoneinfo.py,sha256=oEH-nL3t4h9uawyZqWDtNtDAl6M-CLpLYGI_nI6DulM,1971
|
91
|
-
dycw_utilities-0.
|
92
|
-
dycw_utilities-0.
|
93
|
-
dycw_utilities-0.
|
94
|
-
dycw_utilities-0.
|
91
|
+
dycw_utilities-0.138.1.dist-info/METADATA,sha256=50rXKdwfhrNV9nMtdxwwleIHXmBFm3v6rTZxopeRnV0,1637
|
92
|
+
dycw_utilities-0.138.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
93
|
+
dycw_utilities-0.138.1.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
94
|
+
dycw_utilities-0.138.1.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/click.py
CHANGED
@@ -16,7 +16,7 @@ from utilities.functions import EnsureStrError, ensure_str, get_class_name
|
|
16
16
|
from utilities.iterables import is_iterable_not_str
|
17
17
|
from utilities.parse import ParseObjectError, parse_object
|
18
18
|
from utilities.text import split_str
|
19
|
-
from utilities.whenever import FreqLike, _FreqParseError
|
19
|
+
from utilities.whenever import FreqLike, _FreqParseError
|
20
20
|
|
21
21
|
if TYPE_CHECKING:
|
22
22
|
from collections.abc import Iterable, Sequence
|
@@ -29,12 +29,13 @@ if TYPE_CHECKING:
|
|
29
29
|
IPv4AddressLike,
|
30
30
|
IPv6AddressLike,
|
31
31
|
MaybeStr,
|
32
|
+
MonthDayLike,
|
32
33
|
PlainDateTimeLike,
|
33
34
|
TimeDeltaLike,
|
34
35
|
TimeLike,
|
36
|
+
YearMonthLike,
|
35
37
|
ZonedDateTimeLike,
|
36
38
|
)
|
37
|
-
from utilities.whenever import MonthLike
|
38
39
|
|
39
40
|
|
40
41
|
FilePath = click.Path(file_okay=True, dir_okay=False, path_type=pathlib.Path)
|
@@ -252,10 +253,10 @@ class IPv6Address(ParamType):
|
|
252
253
|
assert_never(never)
|
253
254
|
|
254
255
|
|
255
|
-
class
|
256
|
-
"""A month-
|
256
|
+
class MonthDay(ParamType):
|
257
|
+
"""A month-day parameter."""
|
257
258
|
|
258
|
-
name = "month"
|
259
|
+
name = "month-day"
|
259
260
|
|
260
261
|
@override
|
261
262
|
def __repr__(self) -> str:
|
@@ -263,13 +264,19 @@ class Month(ParamType):
|
|
263
264
|
|
264
265
|
@override
|
265
266
|
def convert(
|
266
|
-
self, value:
|
267
|
-
) ->
|
268
|
-
"""Convert a value into the `
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
267
|
+
self, value: MonthDayLike, param: Parameter | None, ctx: Context | None
|
268
|
+
) -> whenever.MonthDay:
|
269
|
+
"""Convert a value into the `MonthDay` type."""
|
270
|
+
match value:
|
271
|
+
case whenever.MonthDay():
|
272
|
+
return value
|
273
|
+
case str():
|
274
|
+
try:
|
275
|
+
return whenever.MonthDay.parse_common_iso(value)
|
276
|
+
except ValueError as error:
|
277
|
+
self.fail(str(error), param, ctx)
|
278
|
+
case _ as never:
|
279
|
+
assert_never(never)
|
273
280
|
|
274
281
|
|
275
282
|
class PlainDateTime(ParamType):
|
@@ -350,6 +357,32 @@ class TimeDelta(ParamType):
|
|
350
357
|
assert_never(never)
|
351
358
|
|
352
359
|
|
360
|
+
class YearMonth(ParamType):
|
361
|
+
"""A year-month parameter."""
|
362
|
+
|
363
|
+
name = "year-month"
|
364
|
+
|
365
|
+
@override
|
366
|
+
def __repr__(self) -> str:
|
367
|
+
return self.name.upper()
|
368
|
+
|
369
|
+
@override
|
370
|
+
def convert(
|
371
|
+
self, value: YearMonthLike, param: Parameter | None, ctx: Context | None
|
372
|
+
) -> whenever.YearMonth:
|
373
|
+
"""Convert a value into the `YearMonth` type."""
|
374
|
+
match value:
|
375
|
+
case whenever.YearMonth():
|
376
|
+
return value
|
377
|
+
case str():
|
378
|
+
try:
|
379
|
+
return whenever.YearMonth.parse_common_iso(value)
|
380
|
+
except ValueError as error:
|
381
|
+
self.fail(str(error), param, ctx)
|
382
|
+
case _ as never:
|
383
|
+
assert_never(never)
|
384
|
+
|
385
|
+
|
353
386
|
class ZonedDateTime(ParamType):
|
354
387
|
"""A zoned-datetime-valued parameter."""
|
355
388
|
|
@@ -559,8 +592,10 @@ __all__ = [
|
|
559
592
|
"ListEnums",
|
560
593
|
"ListParameter",
|
561
594
|
"ListStrs",
|
595
|
+
"MonthDay",
|
562
596
|
"PlainDateTime",
|
563
597
|
"Time",
|
564
598
|
"TimeDelta",
|
599
|
+
"YearMonth",
|
565
600
|
"ZonedDateTime",
|
566
601
|
]
|
utilities/hypothesis.py
CHANGED
@@ -38,12 +38,14 @@ from whenever import (
|
|
38
38
|
Date,
|
39
39
|
DateDelta,
|
40
40
|
DateTimeDelta,
|
41
|
+
MonthDay,
|
41
42
|
PlainDateTime,
|
42
43
|
RepeatedTime,
|
43
44
|
SkippedTime,
|
44
45
|
Time,
|
45
46
|
TimeDelta,
|
46
47
|
TimeZoneNotFoundError,
|
48
|
+
YearMonth,
|
47
49
|
ZonedDateTime,
|
48
50
|
)
|
49
51
|
|
@@ -76,8 +78,6 @@ from utilities.whenever import (
|
|
76
78
|
DATE_DELTA_MIN,
|
77
79
|
DATE_DELTA_PARSABLE_MAX,
|
78
80
|
DATE_DELTA_PARSABLE_MIN,
|
79
|
-
DATE_MAX,
|
80
|
-
DATE_MIN,
|
81
81
|
DATE_TIME_DELTA_MAX,
|
82
82
|
DATE_TIME_DELTA_MIN,
|
83
83
|
DATE_TIME_DELTA_PARSABLE_MAX,
|
@@ -85,16 +85,9 @@ from utilities.whenever import (
|
|
85
85
|
DATE_TWO_DIGIT_YEAR_MAX,
|
86
86
|
DATE_TWO_DIGIT_YEAR_MIN,
|
87
87
|
DAY,
|
88
|
-
MONTH_MAX,
|
89
|
-
MONTH_MIN,
|
90
|
-
PLAIN_DATE_TIME_MAX,
|
91
|
-
PLAIN_DATE_TIME_MIN,
|
92
88
|
TIME_DELTA_MAX,
|
93
89
|
TIME_DELTA_MIN,
|
94
|
-
TIME_MAX,
|
95
|
-
TIME_MIN,
|
96
90
|
Freq,
|
97
|
-
Month,
|
98
91
|
to_date_time_delta,
|
99
92
|
to_days,
|
100
93
|
to_nanos,
|
@@ -255,14 +248,14 @@ def dates(
|
|
255
248
|
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
256
249
|
match min_value_:
|
257
250
|
case None:
|
258
|
-
min_value_ =
|
251
|
+
min_value_ = Date.MIN
|
259
252
|
case Date():
|
260
253
|
...
|
261
254
|
case _ as never:
|
262
255
|
assert_never(never)
|
263
256
|
match max_value_:
|
264
257
|
case None:
|
265
|
-
max_value_ =
|
258
|
+
max_value_ = Date.MAX
|
266
259
|
case Date():
|
267
260
|
...
|
268
261
|
case _ as never:
|
@@ -695,33 +688,32 @@ def lists_fixed_length[T](
|
|
695
688
|
|
696
689
|
|
697
690
|
@composite
|
698
|
-
def
|
691
|
+
def month_days(
|
699
692
|
draw: DrawFn,
|
700
693
|
/,
|
701
694
|
*,
|
702
|
-
min_value: MaybeSearchStrategy[
|
703
|
-
max_value: MaybeSearchStrategy[
|
704
|
-
|
705
|
-
|
706
|
-
"""Strategy for generating months."""
|
695
|
+
min_value: MaybeSearchStrategy[MonthDay | None] = None,
|
696
|
+
max_value: MaybeSearchStrategy[MonthDay | None] = None,
|
697
|
+
) -> MonthDay:
|
698
|
+
"""Strategy for generating month-days."""
|
707
699
|
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
708
700
|
match min_value_:
|
709
701
|
case None:
|
710
|
-
min_value_ =
|
711
|
-
case
|
702
|
+
min_value_ = MonthDay.MIN
|
703
|
+
case MonthDay():
|
712
704
|
...
|
713
705
|
case _ as never:
|
714
706
|
assert_never(never)
|
715
707
|
match max_value_:
|
716
708
|
case None:
|
717
|
-
max_value_ =
|
718
|
-
case
|
709
|
+
max_value_ = MonthDay.MAX
|
710
|
+
case MonthDay():
|
719
711
|
...
|
720
712
|
case _ as never:
|
721
713
|
assert_never(never)
|
722
|
-
min_date, max_date = [m.
|
723
|
-
date = draw(dates(min_value=min_date, max_value=max_date
|
724
|
-
return
|
714
|
+
min_date, max_date = [m.in_year(2000) for m in [min_value_, max_value_]]
|
715
|
+
date = draw(dates(min_value=min_date, max_value=max_date))
|
716
|
+
return date.month_day()
|
725
717
|
|
726
718
|
|
727
719
|
##
|
@@ -843,14 +835,14 @@ def plain_datetimes(
|
|
843
835
|
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
844
836
|
match min_value_:
|
845
837
|
case None:
|
846
|
-
min_value_ =
|
838
|
+
min_value_ = PlainDateTime.MIN
|
847
839
|
case PlainDateTime():
|
848
840
|
...
|
849
841
|
case _ as never:
|
850
842
|
assert_never(never)
|
851
843
|
match max_value_:
|
852
844
|
case None:
|
853
|
-
max_value_ =
|
845
|
+
max_value_ = PlainDateTime.MAX
|
854
846
|
case PlainDateTime():
|
855
847
|
...
|
856
848
|
case _ as never:
|
@@ -1219,14 +1211,14 @@ def times(
|
|
1219
1211
|
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
1220
1212
|
match min_value_:
|
1221
1213
|
case None:
|
1222
|
-
min_value_ =
|
1214
|
+
min_value_ = Time()
|
1223
1215
|
case Time():
|
1224
1216
|
...
|
1225
1217
|
case _ as never:
|
1226
1218
|
assert_never(never)
|
1227
1219
|
match max_value_:
|
1228
1220
|
case None:
|
1229
|
-
max_value_ =
|
1221
|
+
max_value_ = Time.MAX
|
1230
1222
|
case Time():
|
1231
1223
|
...
|
1232
1224
|
case _ as never:
|
@@ -1308,6 +1300,39 @@ def versions(draw: DrawFn, /, *, suffix: MaybeSearchStrategy[bool] = False) -> V
|
|
1308
1300
|
##
|
1309
1301
|
|
1310
1302
|
|
1303
|
+
@composite
|
1304
|
+
def year_months(
|
1305
|
+
draw: DrawFn,
|
1306
|
+
/,
|
1307
|
+
*,
|
1308
|
+
min_value: MaybeSearchStrategy[YearMonth | None] = None,
|
1309
|
+
max_value: MaybeSearchStrategy[YearMonth | None] = None,
|
1310
|
+
two_digit: MaybeSearchStrategy[bool] = False,
|
1311
|
+
) -> YearMonth:
|
1312
|
+
"""Strategy for generating months."""
|
1313
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
1314
|
+
match min_value_:
|
1315
|
+
case None:
|
1316
|
+
min_value_ = YearMonth.MIN
|
1317
|
+
case YearMonth():
|
1318
|
+
...
|
1319
|
+
case _ as never:
|
1320
|
+
assert_never(never)
|
1321
|
+
match max_value_:
|
1322
|
+
case None:
|
1323
|
+
max_value_ = YearMonth.MAX
|
1324
|
+
case YearMonth():
|
1325
|
+
...
|
1326
|
+
case _ as never:
|
1327
|
+
assert_never(never)
|
1328
|
+
min_date, max_date = [m.on_day(1) for m in [min_value_, max_value_]]
|
1329
|
+
date = draw(dates(min_value=min_date, max_value=max_date, two_digit=two_digit))
|
1330
|
+
return date.year_month()
|
1331
|
+
|
1332
|
+
|
1333
|
+
##
|
1334
|
+
|
1335
|
+
|
1311
1336
|
@composite
|
1312
1337
|
def zoned_datetimes(
|
1313
1338
|
draw: DrawFn,
|
@@ -1345,7 +1370,7 @@ def zoned_datetimes(
|
|
1345
1370
|
):
|
1346
1371
|
zoned = plain.assume_tz(time_zone_.key, disambiguate="raise")
|
1347
1372
|
with assume_does_not_raise(OverflowError, match="date value out of range"):
|
1348
|
-
if not ((
|
1373
|
+
if not ((Date.MIN + DAY) <= zoned.date() <= (Date.MAX - DAY)):
|
1349
1374
|
_ = zoned.py_datetime()
|
1350
1375
|
return zoned
|
1351
1376
|
|
@@ -1373,7 +1398,7 @@ __all__ = [
|
|
1373
1398
|
"int64s",
|
1374
1399
|
"int_arrays",
|
1375
1400
|
"lists_fixed_length",
|
1376
|
-
"
|
1401
|
+
"month_days",
|
1377
1402
|
"namespace_mixins",
|
1378
1403
|
"numbers",
|
1379
1404
|
"pairs",
|
@@ -1399,5 +1424,6 @@ __all__ = [
|
|
1399
1424
|
"uint32s",
|
1400
1425
|
"uint64s",
|
1401
1426
|
"versions",
|
1427
|
+
"year_months",
|
1402
1428
|
"zoned_datetimes",
|
1403
1429
|
]
|
utilities/orjson.py
CHANGED
@@ -26,9 +26,11 @@ from whenever import (
|
|
26
26
|
Date,
|
27
27
|
DateDelta,
|
28
28
|
DateTimeDelta,
|
29
|
+
MonthDay,
|
29
30
|
PlainDateTime,
|
30
31
|
Time,
|
31
32
|
TimeDelta,
|
33
|
+
YearMonth,
|
32
34
|
ZonedDateTime,
|
33
35
|
)
|
34
36
|
|
@@ -71,6 +73,7 @@ class _Prefixes(Enum):
|
|
71
73
|
float_ = "fl"
|
72
74
|
frozenset_ = "fr"
|
73
75
|
list_ = "l"
|
76
|
+
month_day = "md"
|
74
77
|
none = "none"
|
75
78
|
path = "p"
|
76
79
|
plain_date_time = "pd"
|
@@ -81,6 +84,7 @@ class _Prefixes(Enum):
|
|
81
84
|
unserializable = "un"
|
82
85
|
uuid = "uu"
|
83
86
|
version = "v"
|
87
|
+
year_month = "ym"
|
84
88
|
zoned_date_time = "zd"
|
85
89
|
|
86
90
|
|
@@ -172,6 +176,8 @@ def _pre_process(
|
|
172
176
|
if MIN_INT64 <= int_ <= MAX_INT64:
|
173
177
|
return int_
|
174
178
|
raise _SerializeIntegerError(obj=int_)
|
179
|
+
case MonthDay() as month_day:
|
180
|
+
return f"[{_Prefixes.month_day.value}]{month_day!s}"
|
175
181
|
case Path() as path:
|
176
182
|
return f"[{_Prefixes.path.value}]{path!s}"
|
177
183
|
case PlainDateTime() as datetime:
|
@@ -187,7 +193,9 @@ def _pre_process(
|
|
187
193
|
case UUID() as uuid:
|
188
194
|
return f"[{_Prefixes.uuid.value}]{uuid}"
|
189
195
|
case Version() as version:
|
190
|
-
return f"[{_Prefixes.version.value}]{version
|
196
|
+
return f"[{_Prefixes.version.value}]{version}"
|
197
|
+
case YearMonth() as year_month:
|
198
|
+
return f"[{_Prefixes.year_month.value}]{year_month}"
|
191
199
|
case ZonedDateTime() as datetime:
|
192
200
|
return f"[{_Prefixes.zoned_date_time.value}]{datetime}"
|
193
201
|
# contains
|
@@ -339,6 +347,7 @@ def deserialize(
|
|
339
347
|
_DATE_DELTA_PATTERN,
|
340
348
|
_DATE_TIME_DELTA_PATTERN,
|
341
349
|
_FLOAT_PATTERN,
|
350
|
+
_MONTH_DAY_PATTERN,
|
342
351
|
_NONE_PATTERN,
|
343
352
|
_PATH_PATTERN,
|
344
353
|
_PLAIN_DATE_TIME_PATTERN,
|
@@ -346,6 +355,7 @@ def deserialize(
|
|
346
355
|
_TIME_DELTA_PATTERN,
|
347
356
|
_UUID_PATTERN,
|
348
357
|
_VERSION_PATTERN,
|
358
|
+
_YEAR_MONTH_PATTERN,
|
349
359
|
_ZONED_DATE_TIME_PATTERN,
|
350
360
|
) = [
|
351
361
|
re.compile(r"^\[" + p.value + r"\](" + ".*" + ")$")
|
@@ -354,6 +364,7 @@ def deserialize(
|
|
354
364
|
_Prefixes.date_delta,
|
355
365
|
_Prefixes.date_time_delta,
|
356
366
|
_Prefixes.float_,
|
367
|
+
_Prefixes.month_day,
|
357
368
|
_Prefixes.none,
|
358
369
|
_Prefixes.path,
|
359
370
|
_Prefixes.plain_date_time,
|
@@ -361,6 +372,7 @@ def deserialize(
|
|
361
372
|
_Prefixes.time_delta,
|
362
373
|
_Prefixes.uuid,
|
363
374
|
_Prefixes.version,
|
375
|
+
_Prefixes.year_month,
|
364
376
|
_Prefixes.zoned_date_time,
|
365
377
|
]
|
366
378
|
]
|
@@ -413,6 +425,8 @@ def _object_hook(
|
|
413
425
|
return DateTimeDelta.parse_common_iso(match.group(1))
|
414
426
|
if match := _FLOAT_PATTERN.search(text):
|
415
427
|
return float(match.group(1))
|
428
|
+
if match := _MONTH_DAY_PATTERN.search(text):
|
429
|
+
return MonthDay.parse_common_iso(match.group(1))
|
416
430
|
if match := _PATH_PATTERN.search(text):
|
417
431
|
return Path(match.group(1))
|
418
432
|
if match := _PLAIN_DATE_TIME_PATTERN.search(text):
|
@@ -425,6 +439,8 @@ def _object_hook(
|
|
425
439
|
return UUID(match.group(1))
|
426
440
|
if match := _VERSION_PATTERN.search(text):
|
427
441
|
return parse_version(match.group(1))
|
442
|
+
if match := _YEAR_MONTH_PATTERN.search(text):
|
443
|
+
return YearMonth.parse_common_iso(match.group(1))
|
428
444
|
if match := _ZONED_DATE_TIME_PATTERN.search(text):
|
429
445
|
return ZonedDateTime.parse_common_iso(match.group(1))
|
430
446
|
if (
|
utilities/parse.py
CHANGED
@@ -13,9 +13,11 @@ from whenever import (
|
|
13
13
|
Date,
|
14
14
|
DateDelta,
|
15
15
|
DateTimeDelta,
|
16
|
+
MonthDay,
|
16
17
|
PlainDateTime,
|
17
18
|
Time,
|
18
19
|
TimeDelta,
|
20
|
+
YearMonth,
|
19
21
|
ZonedDateTime,
|
20
22
|
)
|
21
23
|
|
@@ -51,7 +53,6 @@ from utilities.typing import (
|
|
51
53
|
is_union_type,
|
52
54
|
)
|
53
55
|
from utilities.version import ParseVersionError, Version, parse_version
|
54
|
-
from utilities.whenever import Month
|
55
56
|
|
56
57
|
if TYPE_CHECKING:
|
57
58
|
from collections.abc import Iterable, Mapping, Sequence
|
@@ -194,10 +195,11 @@ def _parse_object_type(
|
|
194
195
|
Date,
|
195
196
|
DateDelta,
|
196
197
|
DateTimeDelta,
|
197
|
-
|
198
|
+
MonthDay,
|
198
199
|
PlainDateTime,
|
199
200
|
Time,
|
200
201
|
TimeDelta,
|
202
|
+
YearMonth,
|
201
203
|
ZonedDateTime,
|
202
204
|
),
|
203
205
|
):
|
@@ -467,10 +469,11 @@ def serialize_object(
|
|
467
469
|
Date,
|
468
470
|
DateDelta,
|
469
471
|
DateTimeDelta,
|
470
|
-
|
472
|
+
MonthDay,
|
471
473
|
PlainDateTime,
|
472
474
|
Time,
|
473
475
|
TimeDelta,
|
476
|
+
YearMonth,
|
474
477
|
ZonedDateTime,
|
475
478
|
),
|
476
479
|
):
|
utilities/typed_settings.py
CHANGED
@@ -16,9 +16,11 @@ from whenever import (
|
|
16
16
|
Date,
|
17
17
|
DateDelta,
|
18
18
|
DateTimeDelta,
|
19
|
+
MonthDay,
|
19
20
|
PlainDateTime,
|
20
21
|
Time,
|
21
22
|
TimeDelta,
|
23
|
+
YearMonth,
|
22
24
|
ZonedDateTime,
|
23
25
|
)
|
24
26
|
|
@@ -57,10 +59,12 @@ class ExtendedTSConverter(TSConverter):
|
|
57
59
|
(Freq, Freq.parse),
|
58
60
|
(IPv4Address, IPv4Address),
|
59
61
|
(IPv6Address, IPv6Address),
|
62
|
+
(MonthDay, MonthDay.parse_common_iso),
|
60
63
|
(Path, partial(_parse_path, resolve=resolve_paths, pwd=Path.cwd())),
|
61
64
|
(PlainDateTime, PlainDateTime.parse_common_iso),
|
62
65
|
(Time, Time.parse_common_iso),
|
63
66
|
(TimeDelta, TimeDelta.parse_common_iso),
|
67
|
+
(YearMonth, YearMonth.parse_common_iso),
|
64
68
|
(ZonedDateTime, ZonedDateTime.parse_common_iso),
|
65
69
|
]
|
66
70
|
extras = {cls: _make_converter(cls, func) for cls, func in cases}
|
@@ -96,6 +100,7 @@ def _parse_path(
|
|
96
100
|
|
97
101
|
##
|
98
102
|
|
103
|
+
|
99
104
|
_BASE_DIR: Path = Path()
|
100
105
|
|
101
106
|
|
utilities/types.py
CHANGED
@@ -25,9 +25,11 @@ from whenever import (
|
|
25
25
|
Date,
|
26
26
|
DateDelta,
|
27
27
|
DateTimeDelta,
|
28
|
+
MonthDay,
|
28
29
|
PlainDateTime,
|
29
30
|
Time,
|
30
31
|
TimeDelta,
|
32
|
+
YearMonth,
|
31
33
|
ZonedDateTime,
|
32
34
|
)
|
33
35
|
|
@@ -214,9 +216,11 @@ type DateLike = MaybeStr[Date]
|
|
214
216
|
type DateTimeDeltaLike = MaybeStr[DateTimeDelta]
|
215
217
|
type MaybeCallableDate = MaybeCallable[Date]
|
216
218
|
type MaybeCallableZonedDateTime = MaybeCallable[ZonedDateTime]
|
219
|
+
type MonthDayLike = MaybeStr[MonthDay]
|
217
220
|
type PlainDateTimeLike = MaybeStr[PlainDateTime]
|
218
221
|
type TimeDeltaLike = MaybeStr[TimeDelta]
|
219
222
|
type TimeLike = MaybeStr[Time]
|
223
|
+
type YearMonthLike = MaybeStr[YearMonth]
|
220
224
|
type ZonedDateTimeLike = MaybeStr[ZonedDateTime]
|
221
225
|
type DateTimeRoundUnit = Literal[
|
222
226
|
"day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"
|
@@ -265,6 +269,7 @@ __all__ = [
|
|
265
269
|
"MaybeIterableHashable",
|
266
270
|
"MaybeStr",
|
267
271
|
"MaybeType",
|
272
|
+
"MonthDayLike",
|
268
273
|
"Number",
|
269
274
|
"OpenMode",
|
270
275
|
"OptExcInfo",
|
@@ -296,5 +301,6 @@ __all__ = [
|
|
296
301
|
"TupleOrStrMapping",
|
297
302
|
"TypeLike",
|
298
303
|
"WeekDay",
|
304
|
+
"YearMonthLike",
|
299
305
|
"ZonedDateTimeLike",
|
300
306
|
]
|
utilities/whenever.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from collections.abc import Callable, Iterable, Mapping
|
4
|
-
from dataclasses import dataclass
|
4
|
+
from dataclasses import dataclass
|
5
5
|
from functools import cache
|
6
6
|
from logging import LogRecord
|
7
7
|
from statistics import fmean
|
@@ -25,6 +25,7 @@ from whenever import (
|
|
25
25
|
PlainDateTime,
|
26
26
|
Time,
|
27
27
|
TimeDelta,
|
28
|
+
YearMonth,
|
28
29
|
ZonedDateTime,
|
29
30
|
)
|
30
31
|
|
@@ -49,16 +50,8 @@ if TYPE_CHECKING:
|
|
49
50
|
## bounds
|
50
51
|
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
9999, 12, 31, hour=23, minute=59, second=59, nanosecond=999999999
|
55
|
-
)
|
56
|
-
DATE_MIN = PLAIN_DATE_TIME_MIN.date()
|
57
|
-
DATE_MAX = PLAIN_DATE_TIME_MAX.date()
|
58
|
-
TIME_MIN = PLAIN_DATE_TIME_MIN.time()
|
59
|
-
TIME_MAX = PLAIN_DATE_TIME_MIN.time()
|
60
|
-
ZONED_DATE_TIME_MIN = PLAIN_DATE_TIME_MIN.assume_tz(UTC.key)
|
61
|
-
ZONED_DATE_TIME_MAX = PLAIN_DATE_TIME_MAX.assume_tz(UTC.key)
|
53
|
+
ZONED_DATE_TIME_MIN = PlainDateTime.MIN.assume_tz(UTC.key)
|
54
|
+
ZONED_DATE_TIME_MAX = PlainDateTime.MAX.assume_tz(UTC.key)
|
62
55
|
|
63
56
|
|
64
57
|
DATE_TIME_DELTA_MIN = DateTimeDelta(
|
@@ -170,7 +163,7 @@ def format_compact(
|
|
170
163
|
obj_use = date.py_date()
|
171
164
|
fmt_use = "%Y%m%d" if fmt is None else fmt
|
172
165
|
case Time() as time:
|
173
|
-
obj_use = time.py_time()
|
166
|
+
obj_use = time.round().py_time()
|
174
167
|
fmt_use = "%H%M%S" if fmt is None else fmt
|
175
168
|
case PlainDateTime() as datetime:
|
176
169
|
obj_use = datetime.round().py_datetime()
|
@@ -465,124 +458,6 @@ class _MinMaxDatePeriodError(MinMaxDateError):
|
|
465
458
|
##
|
466
459
|
|
467
460
|
|
468
|
-
@dataclass(order=True, unsafe_hash=True, slots=True)
|
469
|
-
class Month:
|
470
|
-
"""Represents a month in time."""
|
471
|
-
|
472
|
-
year: int
|
473
|
-
month: int
|
474
|
-
|
475
|
-
def __post_init__(self) -> None:
|
476
|
-
try:
|
477
|
-
_ = Date(self.year, self.month, 1)
|
478
|
-
except ValueError:
|
479
|
-
raise _MonthInvalidError(year=self.year, month=self.month) from None
|
480
|
-
|
481
|
-
@override
|
482
|
-
def __repr__(self) -> str:
|
483
|
-
return self.format_common_iso()
|
484
|
-
|
485
|
-
@override
|
486
|
-
def __str__(self) -> str:
|
487
|
-
return repr(self)
|
488
|
-
|
489
|
-
def __add__(self, other: Any, /) -> Self:
|
490
|
-
if not isinstance(other, int): # pragma: no cover
|
491
|
-
return NotImplemented
|
492
|
-
years, month = divmod(self.month + other - 1, 12)
|
493
|
-
month += 1
|
494
|
-
year = self.year + years
|
495
|
-
return replace(self, year=year, month=month)
|
496
|
-
|
497
|
-
@overload
|
498
|
-
def __sub__(self, other: Self, /) -> int: ...
|
499
|
-
@overload
|
500
|
-
def __sub__(self, other: int, /) -> Self: ...
|
501
|
-
def __sub__(self, other: Self | int, /) -> Self | int:
|
502
|
-
if isinstance(other, int): # pragma: no cover
|
503
|
-
return self + (-other)
|
504
|
-
if isinstance(other, type(self)):
|
505
|
-
self_as_int = 12 * self.year + self.month
|
506
|
-
other_as_int = 12 * other.year + other.month
|
507
|
-
return self_as_int - other_as_int
|
508
|
-
return NotImplemented # pragma: no cover
|
509
|
-
|
510
|
-
@classmethod
|
511
|
-
def ensure(cls, obj: MonthLike, /) -> Month:
|
512
|
-
"""Ensure the object is a month."""
|
513
|
-
match obj:
|
514
|
-
case Month() as month:
|
515
|
-
return month
|
516
|
-
case str() as text:
|
517
|
-
return cls.parse_common_iso(text)
|
518
|
-
case _ as never:
|
519
|
-
assert_never(never)
|
520
|
-
|
521
|
-
def format_common_iso(self) -> str:
|
522
|
-
return f"{self.year:04}-{self.month:02}"
|
523
|
-
|
524
|
-
@classmethod
|
525
|
-
def from_date(cls, date: Date, /) -> Self:
|
526
|
-
return cls(year=date.year, month=date.month)
|
527
|
-
|
528
|
-
@classmethod
|
529
|
-
def parse_common_iso(cls, text: str, /) -> Self:
|
530
|
-
try:
|
531
|
-
year, month = extract_groups(r"^(\d{2,4})[\-\. ]?(\d{2})$", text)
|
532
|
-
except ExtractGroupsError:
|
533
|
-
raise _MonthParseCommonISOError(text=text) from None
|
534
|
-
return cls(year=cls._parse_year(year), month=int(month))
|
535
|
-
|
536
|
-
def to_date(self, /, *, day: int = 1) -> Date:
|
537
|
-
return Date(self.year, self.month, day)
|
538
|
-
|
539
|
-
@classmethod
|
540
|
-
def _parse_year(cls, year: str, /) -> int:
|
541
|
-
match len(year):
|
542
|
-
case 4:
|
543
|
-
return int(year)
|
544
|
-
case 2:
|
545
|
-
min_year = DATE_TWO_DIGIT_YEAR_MIN.year
|
546
|
-
max_year = DATE_TWO_DIGIT_YEAR_MAX.year
|
547
|
-
years = range(min_year, max_year + 1)
|
548
|
-
(result,) = (y for y in years if y % 100 == int(year))
|
549
|
-
return result
|
550
|
-
case _:
|
551
|
-
raise _MonthParseCommonISOError(text=year) from None
|
552
|
-
|
553
|
-
|
554
|
-
@dataclass(kw_only=True, slots=True)
|
555
|
-
class MonthError(Exception): ...
|
556
|
-
|
557
|
-
|
558
|
-
@dataclass(kw_only=True, slots=True)
|
559
|
-
class _MonthInvalidError(MonthError):
|
560
|
-
year: int
|
561
|
-
month: int
|
562
|
-
|
563
|
-
@override
|
564
|
-
def __str__(self) -> str:
|
565
|
-
return f"Invalid year and month: {self.year}, {self.month}"
|
566
|
-
|
567
|
-
|
568
|
-
@dataclass(kw_only=True, slots=True)
|
569
|
-
class _MonthParseCommonISOError(MonthError):
|
570
|
-
text: str
|
571
|
-
|
572
|
-
@override
|
573
|
-
def __str__(self) -> str:
|
574
|
-
return f"Unable to parse month; got {self.text!r}"
|
575
|
-
|
576
|
-
|
577
|
-
type DateOrMonth = Date | Month
|
578
|
-
type MonthLike = MaybeStr[Month]
|
579
|
-
MONTH_MIN = Month.from_date(DATE_MIN)
|
580
|
-
MONTH_MAX = Month.from_date(DATE_MAX)
|
581
|
-
|
582
|
-
|
583
|
-
##
|
584
|
-
|
585
|
-
|
586
461
|
@overload
|
587
462
|
def to_date(*, date: MaybeCallableDate) -> Date: ...
|
588
463
|
@overload
|
@@ -796,6 +671,18 @@ def to_zoned_date_time(
|
|
796
671
|
##
|
797
672
|
|
798
673
|
|
674
|
+
def two_digit_year_month(year: int, month: int, /) -> YearMonth:
|
675
|
+
"""Construct a year-month from a 2-digit year."""
|
676
|
+
min_year = DATE_TWO_DIGIT_YEAR_MIN.year
|
677
|
+
max_year = DATE_TWO_DIGIT_YEAR_MAX.year
|
678
|
+
years = range(min_year, max_year + 1)
|
679
|
+
(year_use,) = (y for y in years if y % 100 == year)
|
680
|
+
return YearMonth(year_use, month)
|
681
|
+
|
682
|
+
|
683
|
+
##
|
684
|
+
|
685
|
+
|
799
686
|
class WheneverLogRecord(LogRecord):
|
800
687
|
"""Log record powered by `whenever`."""
|
801
688
|
|
@@ -851,8 +738,6 @@ __all__ = [
|
|
851
738
|
"DATE_DELTA_MIN",
|
852
739
|
"DATE_DELTA_PARSABLE_MAX",
|
853
740
|
"DATE_DELTA_PARSABLE_MIN",
|
854
|
-
"DATE_MAX",
|
855
|
-
"DATE_MIN",
|
856
741
|
"DATE_TIME_DELTA_MAX",
|
857
742
|
"DATE_TIME_DELTA_MIN",
|
858
743
|
"DATE_TIME_DELTA_PARSABLE_MAX",
|
@@ -865,16 +750,10 @@ __all__ = [
|
|
865
750
|
"MILLISECOND",
|
866
751
|
"MINUTE",
|
867
752
|
"MONTH",
|
868
|
-
"MONTH_MAX",
|
869
|
-
"MONTH_MIN",
|
870
753
|
"NOW_LOCAL",
|
871
|
-
"PLAIN_DATE_TIME_MAX",
|
872
|
-
"PLAIN_DATE_TIME_MIN",
|
873
754
|
"SECOND",
|
874
755
|
"TIME_DELTA_MAX",
|
875
756
|
"TIME_DELTA_MIN",
|
876
|
-
"TIME_MAX",
|
877
|
-
"TIME_MIN",
|
878
757
|
"TODAY_LOCAL",
|
879
758
|
"TODAY_UTC",
|
880
759
|
"WEEK",
|
@@ -883,15 +762,11 @@ __all__ = [
|
|
883
762
|
"ZERO_TIME",
|
884
763
|
"ZONED_DATE_TIME_MAX",
|
885
764
|
"ZONED_DATE_TIME_MIN",
|
886
|
-
"DateOrMonth",
|
887
765
|
"Freq",
|
888
766
|
"FreqError",
|
889
767
|
"FreqLike",
|
890
768
|
"MeanDateTimeError",
|
891
769
|
"MinMaxDateError",
|
892
|
-
"Month",
|
893
|
-
"MonthError",
|
894
|
-
"MonthLike",
|
895
770
|
"ToDaysError",
|
896
771
|
"ToNanosError",
|
897
772
|
"WheneverLogRecord",
|
@@ -912,4 +787,5 @@ __all__ = [
|
|
912
787
|
"to_local_plain",
|
913
788
|
"to_nanos",
|
914
789
|
"to_zoned_date_time",
|
790
|
+
"two_digit_year_month",
|
915
791
|
]
|
File without changes
|
File without changes
|