dycw-utilities 0.131.17__py3-none-any.whl → 0.131.18__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.17.dist-info → dycw_utilities-0.131.18.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.17.dist-info → dycw_utilities-0.131.18.dist-info}/RECORD +11 -11
- utilities/__init__.py +1 -1
- utilities/datetime.py +2 -279
- utilities/iterables.py +1 -30
- utilities/operator.py +21 -15
- utilities/orjson.py +67 -84
- utilities/parse.py +24 -53
- utilities/whenever.py +2 -216
- {dycw_utilities-0.131.17.dist-info → dycw_utilities-0.131.18.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.17.dist-info → dycw_utilities-0.131.18.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=653kI0IdCJCyIvZc8ekfN8j8dOnjrRtGJKRx9UDXVvM,61
|
2
2
|
utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
|
3
3
|
utilities/altair.py,sha256=HeZBVUocjkrTNwwKrClppsIqgNFF-ykv05HfZSoHYno,9104
|
4
4
|
utilities/asyncio.py,sha256=mHnlSA4KPeDaBRts8Rn4sNA_4urodj7gQzs-_8Z-F7A,37587
|
@@ -12,7 +12,7 @@ utilities/contextvars.py,sha256=RsSGGrbQqqZ67rOydnM7WWIsM2lIE31UHJLejnHJPWY,505
|
|
12
12
|
utilities/cryptography.py,sha256=_CiK_K6c_-uQuUhsUNjNjTL-nqxAh4_1zTfS11Xe120,972
|
13
13
|
utilities/cvxpy.py,sha256=Rv1-fD-XYerosCavRF8Pohop2DBkU3AlFaGTfD8AEAA,13776
|
14
14
|
utilities/dataclasses.py,sha256=iiC1wpGXWhaocIikzwBt8bbLWyImoUlOlcDZJGejaIg,33011
|
15
|
-
utilities/datetime.py,sha256=
|
15
|
+
utilities/datetime.py,sha256=bt9tgxHfjSbl0g5H7W2YSwNm-1HsbKjX5AsYcyEO1mU,30361
|
16
16
|
utilities/enum.py,sha256=HoRwVCWzsnH0vpO9ZEcAAIZLMv0Sn2vJxxA4sYMQgDs,5793
|
17
17
|
utilities/errors.py,sha256=nC7ZYtxxDBMfrTHtT_MByBfup_wfGQFRo3eDt-0ZPe8,1045
|
18
18
|
utilities/eventkit.py,sha256=6M5Xu1SzN-juk9PqBHwy5dS-ta7T0qA6SMpDsakOJ0E,13039
|
@@ -28,7 +28,7 @@ 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
|
31
|
-
utilities/iterables.py,sha256=
|
31
|
+
utilities/iterables.py,sha256=cuebB4ivKlZuKm8S3PQIfjavn9h-5mBGmvYq4FpSxFg,43812
|
32
32
|
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
|
@@ -39,11 +39,11 @@ utilities/memory_profiler.py,sha256=tf2C51P2lCujPGvRt2Rfc7VEw5LDXmVPCG3z_AvBmbU,
|
|
39
39
|
utilities/modules.py,sha256=iuvLluJya-hvl1Q25-Jk3dLgx2Es3ck4SjJiEkAlVTs,3195
|
40
40
|
utilities/more_itertools.py,sha256=tBbjjKx8_Uv_TCjxhPwrGfAx_jRHtvLIZqXVWAsjzqA,8842
|
41
41
|
utilities/numpy.py,sha256=Xn23sA2ZbVNqwUYEgNJD3XBYH6IbCri_WkHSNhg3NkY,26122
|
42
|
-
utilities/operator.py,sha256=
|
42
|
+
utilities/operator.py,sha256=DuiWdkmK0D-ddvFqOayDkazTQE1Ysvtl6-UiBN5gns8,3857
|
43
43
|
utilities/optuna.py,sha256=loyJGWTzljgdJaoLhP09PT8Jz6o_pwBOwehY33lHkhw,1923
|
44
|
-
utilities/orjson.py,sha256=
|
44
|
+
utilities/orjson.py,sha256=7CraMEtaJtKTDTTVS4fQU6PeuFcG0V8SGwD3sqZ8kSI,36475
|
45
45
|
utilities/os.py,sha256=D_FyyT-6TtqiN9KSS7c9g1fnUtgxmyMtzAjmYLkk46A,3587
|
46
|
-
utilities/parse.py,sha256=
|
46
|
+
utilities/parse.py,sha256=YE2VWYKDm9WYf5wcOWrOxVfM6UWvhkSxSkwdxRQNsA0,17633
|
47
47
|
utilities/pathlib.py,sha256=PK41rf1c9Wqv7h8f5R7H3_Lhq_gQZTUJD5tu3gMHVaU,3247
|
48
48
|
utilities/period.py,sha256=opqpBevBGSGXbA7NYfRJjtthi1JPxdMaZ7QV3xosnTc,4774
|
49
49
|
utilities/pickle.py,sha256=MBT2xZCsv0pH868IXLGKnlcqNx2IRVKYNpRcqiQQqxw,653
|
@@ -87,11 +87,11 @@ utilities/tzlocal.py,sha256=xbBBzVIUKMk8AkhuIp1qxGRNBioIa5I09dpeoBnIOOU,662
|
|
87
87
|
utilities/uuid.py,sha256=jJTFxz-CWgltqNuzmythB7iEQ-Q1mCwPevUfKthZT3c,611
|
88
88
|
utilities/version.py,sha256=ufhJMmI6KPs1-3wBI71aj5wCukd3sP_m11usLe88DNA,5117
|
89
89
|
utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
90
|
-
utilities/whenever.py,sha256=
|
90
|
+
utilities/whenever.py,sha256=tUNfGB_72YSCxbDKiQ7ulWtISC3AgVBrW1s3XlFPkuw,6883
|
91
91
|
utilities/whenever2.py,sha256=Jd_fEavXCWTdMj29L7j-HbeMvrl8e3Ah_nxcLEFAwOU,12026
|
92
92
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
93
93
|
utilities/zoneinfo.py,sha256=gJPr9l7V8s3Y7TXpCGYEM1S81Rplb9e4MoV9Nvy2VU8,1852
|
94
|
-
dycw_utilities-0.131.
|
95
|
-
dycw_utilities-0.131.
|
96
|
-
dycw_utilities-0.131.
|
97
|
-
dycw_utilities-0.131.
|
94
|
+
dycw_utilities-0.131.18.dist-info/METADATA,sha256=nfjJB5vwmQGGegP4Ma-fxV0cKOE67JbPGxKLmZ0Rd78,1585
|
95
|
+
dycw_utilities-0.131.18.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
96
|
+
dycw_utilities-0.131.18.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
97
|
+
dycw_utilities-0.131.18.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/datetime.py
CHANGED
@@ -11,7 +11,6 @@ from typing import (
|
|
11
11
|
Literal,
|
12
12
|
Self,
|
13
13
|
SupportsFloat,
|
14
|
-
TypeGuard,
|
15
14
|
assert_never,
|
16
15
|
overload,
|
17
16
|
override,
|
@@ -23,7 +22,7 @@ from utilities.platform import SYSTEM
|
|
23
22
|
from utilities.sentinel import Sentinel, sentinel
|
24
23
|
from utilities.types import MaybeCallablePyDate, MaybeCallablePyDateTime, MaybeStr
|
25
24
|
from utilities.typing import is_instance_gen
|
26
|
-
from utilities.zoneinfo import UTC, ensure_time_zone
|
25
|
+
from utilities.zoneinfo import UTC, ensure_time_zone
|
27
26
|
|
28
27
|
if TYPE_CHECKING:
|
29
28
|
from collections.abc import Iterator
|
@@ -114,50 +113,6 @@ class AddWeekdaysError(Exception): ...
|
|
114
113
|
##
|
115
114
|
|
116
115
|
|
117
|
-
def are_equal_date_durations(x: Duration, y: Duration, /) -> bool:
|
118
|
-
"""Check if x == y for durations."""
|
119
|
-
x_timedelta = date_duration_to_timedelta(x)
|
120
|
-
y_timedelta = date_duration_to_timedelta(y)
|
121
|
-
return x_timedelta == y_timedelta
|
122
|
-
|
123
|
-
|
124
|
-
##
|
125
|
-
|
126
|
-
|
127
|
-
def are_equal_dates_or_datetimes(
|
128
|
-
x: DateOrDateTime, y: DateOrDateTime, /, *, strict: bool = False
|
129
|
-
) -> bool:
|
130
|
-
"""Check if x == y for dates/datetimes."""
|
131
|
-
if is_instance_gen(x, dt.date) and is_instance_gen(y, dt.date):
|
132
|
-
return x == y
|
133
|
-
if is_instance_gen(x, dt.datetime) and is_instance_gen(y, dt.datetime):
|
134
|
-
return are_equal_datetimes(x, y, strict=strict)
|
135
|
-
raise AreEqualDatesOrDateTimesError(x=x, y=y)
|
136
|
-
|
137
|
-
|
138
|
-
@dataclass(kw_only=True, slots=True)
|
139
|
-
class AreEqualDatesOrDateTimesError(Exception):
|
140
|
-
x: DateOrDateTime
|
141
|
-
y: DateOrDateTime
|
142
|
-
|
143
|
-
@override
|
144
|
-
def __str__(self) -> str:
|
145
|
-
return f"Cannot compare date and datetime ({self.x}, {self.y})"
|
146
|
-
|
147
|
-
|
148
|
-
##
|
149
|
-
|
150
|
-
|
151
|
-
def are_equal_datetime_durations(x: Duration, y: Duration, /) -> bool:
|
152
|
-
"""Check if x == y for durations."""
|
153
|
-
x_timedelta = datetime_duration_to_timedelta(x)
|
154
|
-
y_timedelta = datetime_duration_to_timedelta(y)
|
155
|
-
return x_timedelta == y_timedelta
|
156
|
-
|
157
|
-
|
158
|
-
##
|
159
|
-
|
160
|
-
|
161
116
|
def are_equal_datetimes(
|
162
117
|
x: dt.datetime, y: dt.datetime, /, *, strict: bool = False
|
163
118
|
) -> bool:
|
@@ -186,16 +141,6 @@ class AreEqualDateTimesError(Exception):
|
|
186
141
|
##
|
187
142
|
|
188
143
|
|
189
|
-
def are_equal_months(x: DateOrMonth, y: DateOrMonth, /) -> bool:
|
190
|
-
"""Check if x == y as months."""
|
191
|
-
x_month = Month.from_date(x) if isinstance(x, dt.date) else x
|
192
|
-
y_month = Month.from_date(y) if isinstance(y, dt.date) else y
|
193
|
-
return x_month == y_month
|
194
|
-
|
195
|
-
|
196
|
-
##
|
197
|
-
|
198
|
-
|
199
144
|
def check_date_not_datetime(date: dt.date, /) -> None:
|
200
145
|
"""Check if a date is not a datetime."""
|
201
146
|
if not is_instance_gen(date, dt.date):
|
@@ -214,19 +159,6 @@ class CheckDateNotDateTimeError(Exception):
|
|
214
159
|
##
|
215
160
|
|
216
161
|
|
217
|
-
def date_to_datetime(
|
218
|
-
date: dt.date, /, *, time: dt.time | None = None, time_zone: TimeZoneLike = UTC
|
219
|
-
) -> dt.datetime:
|
220
|
-
"""Expand a date into a datetime."""
|
221
|
-
check_date_not_datetime(date)
|
222
|
-
time_use = dt.time(0) if time is None else time
|
223
|
-
time_zone_use = ensure_time_zone(time_zone)
|
224
|
-
return dt.datetime.combine(date, time_use, tzinfo=time_zone_use)
|
225
|
-
|
226
|
-
|
227
|
-
##
|
228
|
-
|
229
|
-
|
230
162
|
def date_to_month(date: dt.date, /) -> Month:
|
231
163
|
"""Collapse a date into a month."""
|
232
164
|
check_date_not_datetime(date)
|
@@ -337,32 +269,6 @@ def datetime_duration_to_microseconds(duration: Duration, /) -> int:
|
|
337
269
|
)
|
338
270
|
|
339
271
|
|
340
|
-
@overload
|
341
|
-
def datetime_duration_to_milliseconds(
|
342
|
-
duration: Duration, /, *, strict: Literal[True]
|
343
|
-
) -> int: ...
|
344
|
-
@overload
|
345
|
-
def datetime_duration_to_milliseconds(
|
346
|
-
duration: Duration, /, *, strict: bool = False
|
347
|
-
) -> float: ...
|
348
|
-
def datetime_duration_to_milliseconds(
|
349
|
-
duration: Duration, /, *, strict: bool = False
|
350
|
-
) -> int | float:
|
351
|
-
"""Compute the number of milliseconds in a datetime duration."""
|
352
|
-
timedelta = datetime_duration_to_timedelta(duration)
|
353
|
-
microseconds = datetime_duration_to_microseconds(timedelta)
|
354
|
-
milliseconds, remainder = divmod(microseconds, _MICROSECONDS_PER_MILLISECOND)
|
355
|
-
match remainder, strict:
|
356
|
-
case 0, _:
|
357
|
-
return milliseconds
|
358
|
-
case _, True:
|
359
|
-
raise TimedeltaToMillisecondsError(duration=duration, remainder=remainder)
|
360
|
-
case _, False:
|
361
|
-
return milliseconds + remainder / _MICROSECONDS_PER_MILLISECOND
|
362
|
-
case _ as never:
|
363
|
-
assert_never(never)
|
364
|
-
|
365
|
-
|
366
272
|
@dataclass(kw_only=True, slots=True)
|
367
273
|
class TimedeltaToMillisecondsError(Exception):
|
368
274
|
duration: Duration
|
@@ -370,7 +276,7 @@ class TimedeltaToMillisecondsError(Exception):
|
|
370
276
|
|
371
277
|
@override
|
372
278
|
def __str__(self) -> str:
|
373
|
-
return f"Unable to convert {self.duration} to milliseconds; got {self.remainder} microsecond(s)"
|
279
|
+
return f"Unable to convert {self.duration} to milliseconds; got {self.remainder} microsecond(s)" # pragma: no cover
|
374
280
|
|
375
281
|
|
376
282
|
def datetime_duration_to_timedelta(duration: Duration, /) -> dt.timedelta:
|
@@ -413,20 +319,6 @@ def datetime_utc(
|
|
413
319
|
##
|
414
320
|
|
415
321
|
|
416
|
-
def days_since_epoch(date: dt.date, /) -> int:
|
417
|
-
"""Compute the number of days since the epoch."""
|
418
|
-
check_date_not_datetime(date)
|
419
|
-
return timedelta_since_epoch(date).days
|
420
|
-
|
421
|
-
|
422
|
-
def days_since_epoch_to_date(days: int, /) -> dt.date:
|
423
|
-
"""Convert a number of days since the epoch to a date."""
|
424
|
-
return EPOCH_DATE + days * DAY
|
425
|
-
|
426
|
-
|
427
|
-
##
|
428
|
-
|
429
|
-
|
430
322
|
def ensure_month(month: MonthLike, /) -> Month:
|
431
323
|
"""Ensure the object is a month."""
|
432
324
|
if isinstance(month, Month):
|
@@ -449,25 +341,6 @@ class EnsureMonthError(Exception):
|
|
449
341
|
##
|
450
342
|
|
451
343
|
|
452
|
-
def format_datetime_local_and_utc(datetime: dt.datetime, /) -> str:
|
453
|
-
"""Format a plain datetime locally & in UTC."""
|
454
|
-
time_zone = ensure_time_zone(datetime)
|
455
|
-
if time_zone is UTC:
|
456
|
-
return datetime.strftime("%Y-%m-%d %H:%M:%S (%a, UTC)")
|
457
|
-
as_utc = datetime.astimezone(UTC)
|
458
|
-
local = get_time_zone_name(time_zone)
|
459
|
-
if datetime.year != as_utc.year:
|
460
|
-
return f"{datetime:%Y-%m-%d %H:%M:%S (%a}, {local}, {as_utc:%Y-%m-%d %H:%M:%S} UTC)"
|
461
|
-
if (datetime.month != as_utc.month) or (datetime.day != as_utc.day):
|
462
|
-
return (
|
463
|
-
f"{datetime:%Y-%m-%d %H:%M:%S (%a}, {local}, {as_utc:%m-%d %H:%M:%S} UTC)"
|
464
|
-
)
|
465
|
-
return f"{datetime:%Y-%m-%d %H:%M:%S (%a}, {local}, {as_utc:%H:%M:%S} UTC)"
|
466
|
-
|
467
|
-
|
468
|
-
##
|
469
|
-
|
470
|
-
|
471
344
|
@overload
|
472
345
|
def get_date(*, date: MaybeCallablePyDate) -> dt.date: ...
|
473
346
|
@overload
|
@@ -691,14 +564,6 @@ def is_integral_timedelta(duration: Duration, /) -> bool:
|
|
691
564
|
##
|
692
565
|
|
693
566
|
|
694
|
-
def is_plain_datetime(obj: Any, /) -> TypeGuard[dt.datetime]:
|
695
|
-
"""Check if an object is a plain datetime."""
|
696
|
-
return isinstance(obj, dt.datetime) and (obj.tzinfo is None)
|
697
|
-
|
698
|
-
|
699
|
-
##
|
700
|
-
|
701
|
-
|
702
567
|
_FRIDAY = 5
|
703
568
|
|
704
569
|
|
@@ -719,14 +584,6 @@ def is_zero_time(duration: Duration, /) -> bool:
|
|
719
584
|
##
|
720
585
|
|
721
586
|
|
722
|
-
def is_zoned_datetime(obj: Any, /) -> TypeGuard[dt.datetime]:
|
723
|
-
"""Check if an object is a zoned datetime."""
|
724
|
-
return isinstance(obj, dt.datetime) and (obj.tzinfo is not None)
|
725
|
-
|
726
|
-
|
727
|
-
##
|
728
|
-
|
729
|
-
|
730
587
|
def maybe_sub_pct_y(text: str, /) -> str:
|
731
588
|
"""Substitute the `%Y' token with '%4Y' if necessary."""
|
732
589
|
match SYSTEM:
|
@@ -839,54 +696,6 @@ def microseconds_since_epoch_to_datetime(
|
|
839
696
|
##
|
840
697
|
|
841
698
|
|
842
|
-
@overload
|
843
|
-
def milliseconds_since_epoch(
|
844
|
-
datetime: dt.datetime, /, *, strict: Literal[True]
|
845
|
-
) -> int: ...
|
846
|
-
@overload
|
847
|
-
def milliseconds_since_epoch(
|
848
|
-
datetime: dt.datetime, /, *, strict: bool = False
|
849
|
-
) -> float: ...
|
850
|
-
def milliseconds_since_epoch(
|
851
|
-
datetime: dt.datetime, /, *, strict: bool = False
|
852
|
-
) -> float:
|
853
|
-
"""Compute the number of milliseconds since the epoch."""
|
854
|
-
microseconds = microseconds_since_epoch(datetime)
|
855
|
-
milliseconds, remainder = divmod(microseconds, _MICROSECONDS_PER_MILLISECOND)
|
856
|
-
if strict:
|
857
|
-
if remainder == 0:
|
858
|
-
return milliseconds
|
859
|
-
raise MillisecondsSinceEpochError(datetime=datetime, remainder=remainder)
|
860
|
-
return milliseconds + remainder / _MICROSECONDS_PER_MILLISECOND
|
861
|
-
|
862
|
-
|
863
|
-
@dataclass(kw_only=True, slots=True)
|
864
|
-
class MillisecondsSinceEpochError(Exception):
|
865
|
-
datetime: dt.datetime
|
866
|
-
remainder: int
|
867
|
-
|
868
|
-
@override
|
869
|
-
def __str__(self) -> str:
|
870
|
-
return f"Unable to convert {self.datetime} to milliseconds since epoch; got {self.remainder} microsecond(s)"
|
871
|
-
|
872
|
-
|
873
|
-
def milliseconds_since_epoch_to_datetime(
|
874
|
-
milliseconds: int, /, *, time_zone: dt.tzinfo | None = None
|
875
|
-
) -> dt.datetime:
|
876
|
-
"""Convert a number of milliseconds since the epoch to a datetime."""
|
877
|
-
epoch = EPOCH_NAIVE if time_zone is None else EPOCH_UTC
|
878
|
-
timedelta = milliseconds_to_timedelta(milliseconds)
|
879
|
-
return epoch + timedelta
|
880
|
-
|
881
|
-
|
882
|
-
def milliseconds_to_timedelta(milliseconds: int, /) -> dt.timedelta:
|
883
|
-
"""Compute a timedelta given a number of milliseconds."""
|
884
|
-
return microseconds_to_timedelta(_MICROSECONDS_PER_MILLISECOND * milliseconds)
|
885
|
-
|
886
|
-
|
887
|
-
##
|
888
|
-
|
889
|
-
|
890
699
|
@dataclass(order=True, unsafe_hash=True, slots=True)
|
891
700
|
class Month:
|
892
701
|
"""Represents a month in time."""
|
@@ -1070,70 +879,6 @@ def _round_to_weekday(
|
|
1070
879
|
##
|
1071
880
|
|
1072
881
|
|
1073
|
-
def serialize_compact(date_or_datetime: DateOrDateTime, /) -> str:
|
1074
|
-
"""Serialize a date/datetime using a compact format."""
|
1075
|
-
match date_or_datetime:
|
1076
|
-
case dt.datetime() as datetime:
|
1077
|
-
if datetime.tzinfo is None:
|
1078
|
-
raise SerializeCompactError(datetime=datetime)
|
1079
|
-
format_ = "%Y%m%dT%H%M%S"
|
1080
|
-
case dt.date():
|
1081
|
-
format_ = "%Y%m%d"
|
1082
|
-
case _ as never:
|
1083
|
-
assert_never(never)
|
1084
|
-
return date_or_datetime.strftime(maybe_sub_pct_y(format_))
|
1085
|
-
|
1086
|
-
|
1087
|
-
@dataclass(kw_only=True, slots=True)
|
1088
|
-
class SerializeCompactError(Exception):
|
1089
|
-
datetime: dt.datetime
|
1090
|
-
|
1091
|
-
@override
|
1092
|
-
def __str__(self) -> str:
|
1093
|
-
return f"Unable to serialize plain datetime {self.datetime}"
|
1094
|
-
|
1095
|
-
|
1096
|
-
def parse_date_compact(text: str, /) -> dt.date:
|
1097
|
-
"""Parse a compact string into a date."""
|
1098
|
-
try:
|
1099
|
-
datetime = dt.datetime.strptime(text, "%Y%m%d").replace(tzinfo=UTC)
|
1100
|
-
except ValueError:
|
1101
|
-
raise ParseDateCompactError(text=text) from None
|
1102
|
-
return datetime.date()
|
1103
|
-
|
1104
|
-
|
1105
|
-
@dataclass(kw_only=True, slots=True)
|
1106
|
-
class ParseDateCompactError(Exception):
|
1107
|
-
text: str
|
1108
|
-
|
1109
|
-
@override
|
1110
|
-
def __str__(self) -> str:
|
1111
|
-
return f"Unable to parse {self.text!r} into a date"
|
1112
|
-
|
1113
|
-
|
1114
|
-
def parse_datetime_compact(
|
1115
|
-
text: str, /, *, time_zone: TimeZoneLike = UTC
|
1116
|
-
) -> dt.datetime:
|
1117
|
-
"""Parse a compact string into a datetime."""
|
1118
|
-
time_zone = ensure_time_zone(time_zone)
|
1119
|
-
try:
|
1120
|
-
return dt.datetime.strptime(text, "%Y%m%dT%H%M%S").replace(tzinfo=time_zone)
|
1121
|
-
except ValueError:
|
1122
|
-
raise ParseDateTimeCompactError(text=text) from None
|
1123
|
-
|
1124
|
-
|
1125
|
-
@dataclass(kw_only=True, slots=True)
|
1126
|
-
class ParseDateTimeCompactError(Exception):
|
1127
|
-
text: str
|
1128
|
-
|
1129
|
-
@override
|
1130
|
-
def __str__(self) -> str:
|
1131
|
-
return f"Unable to parse {self.text!r} into a datetime"
|
1132
|
-
|
1133
|
-
|
1134
|
-
##
|
1135
|
-
|
1136
|
-
|
1137
882
|
def serialize_month(month: Month, /) -> str:
|
1138
883
|
"""Serialize a month."""
|
1139
884
|
return f"{month.year:04}-{month.month:02}"
|
@@ -1323,46 +1068,32 @@ __all__ = [
|
|
1323
1068
|
"AddDurationError",
|
1324
1069
|
"AddWeekdaysError",
|
1325
1070
|
"AreEqualDateTimesError",
|
1326
|
-
"AreEqualDatesOrDateTimesError",
|
1327
1071
|
"CheckDateNotDateTimeError",
|
1328
1072
|
"DateOrMonth",
|
1329
1073
|
"EnsureMonthError",
|
1330
1074
|
"GetMinMaxDateError",
|
1331
1075
|
"MeanDateTimeError",
|
1332
1076
|
"MeanTimeDeltaError",
|
1333
|
-
"MillisecondsSinceEpochError",
|
1334
1077
|
"Month",
|
1335
1078
|
"MonthError",
|
1336
1079
|
"MonthLike",
|
1337
|
-
"ParseDateCompactError",
|
1338
|
-
"ParseDateTimeCompactError",
|
1339
1080
|
"ParseMonthError",
|
1340
|
-
"SerializeCompactError",
|
1341
1081
|
"SubDurationError",
|
1342
1082
|
"TimedeltaToMillisecondsError",
|
1343
1083
|
"YieldDaysError",
|
1344
1084
|
"YieldWeekdaysError",
|
1345
1085
|
"add_duration",
|
1346
1086
|
"add_weekdays",
|
1347
|
-
"are_equal_date_durations",
|
1348
|
-
"are_equal_dates_or_datetimes",
|
1349
|
-
"are_equal_datetime_durations",
|
1350
1087
|
"are_equal_datetimes",
|
1351
|
-
"are_equal_months",
|
1352
1088
|
"check_date_not_datetime",
|
1353
1089
|
"date_duration_to_int",
|
1354
1090
|
"date_duration_to_timedelta",
|
1355
|
-
"date_to_datetime",
|
1356
1091
|
"date_to_month",
|
1357
1092
|
"datetime_duration_to_float",
|
1358
1093
|
"datetime_duration_to_microseconds",
|
1359
|
-
"datetime_duration_to_milliseconds",
|
1360
1094
|
"datetime_duration_to_timedelta",
|
1361
1095
|
"datetime_utc",
|
1362
|
-
"days_since_epoch",
|
1363
|
-
"days_since_epoch_to_date",
|
1364
1096
|
"ensure_month",
|
1365
|
-
"format_datetime_local_and_utc",
|
1366
1097
|
"get_date",
|
1367
1098
|
"get_datetime",
|
1368
1099
|
"get_half_years",
|
@@ -1373,27 +1104,19 @@ __all__ = [
|
|
1373
1104
|
"get_today",
|
1374
1105
|
"get_years",
|
1375
1106
|
"is_integral_timedelta",
|
1376
|
-
"is_plain_datetime",
|
1377
1107
|
"is_weekday",
|
1378
1108
|
"is_zero_time",
|
1379
|
-
"is_zoned_datetime",
|
1380
1109
|
"maybe_sub_pct_y",
|
1381
1110
|
"mean_datetime",
|
1382
1111
|
"mean_timedelta",
|
1383
1112
|
"microseconds_since_epoch",
|
1384
1113
|
"microseconds_since_epoch_to_datetime",
|
1385
1114
|
"microseconds_to_timedelta",
|
1386
|
-
"milliseconds_since_epoch",
|
1387
|
-
"milliseconds_since_epoch_to_datetime",
|
1388
|
-
"milliseconds_to_timedelta",
|
1389
|
-
"parse_date_compact",
|
1390
|
-
"parse_datetime_compact",
|
1391
1115
|
"parse_month",
|
1392
1116
|
"parse_two_digit_year",
|
1393
1117
|
"round_datetime",
|
1394
1118
|
"round_to_next_weekday",
|
1395
1119
|
"round_to_prev_weekday",
|
1396
|
-
"serialize_compact",
|
1397
1120
|
"serialize_month",
|
1398
1121
|
"sub_duration",
|
1399
1122
|
"timedelta_since_epoch",
|
utilities/iterables.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import builtins
|
4
|
-
import datetime as dt
|
5
4
|
from collections import Counter
|
6
5
|
from collections.abc import (
|
7
6
|
Callable,
|
@@ -34,7 +33,7 @@ from typing import (
|
|
34
33
|
)
|
35
34
|
|
36
35
|
from utilities.errors import ImpossibleCaseError
|
37
|
-
from utilities.functions import ensure_hashable,
|
36
|
+
from utilities.functions import ensure_hashable, ensure_str
|
38
37
|
from utilities.math import (
|
39
38
|
_CheckIntegerEqualError,
|
40
39
|
_CheckIntegerEqualOrApproxError,
|
@@ -45,7 +44,6 @@ from utilities.math import (
|
|
45
44
|
from utilities.reprlib import get_repr
|
46
45
|
from utilities.sentinel import Sentinel, sentinel
|
47
46
|
from utilities.types import Sign, THashable, TSupportsAdd, TSupportsLT
|
48
|
-
from utilities.zoneinfo import UTC
|
49
47
|
|
50
48
|
if TYPE_CHECKING:
|
51
49
|
from types import NoneType
|
@@ -1326,9 +1324,6 @@ def _sort_iterable_cmp(x: Any, y: Any, /) -> Sign:
|
|
1326
1324
|
if x is None:
|
1327
1325
|
y = cast("NoneType", y)
|
1328
1326
|
return 0
|
1329
|
-
if isinstance(x, dt.datetime):
|
1330
|
-
y = cast("dt.datetime", y)
|
1331
|
-
return _sort_iterable_cmp_datetimes(x, y)
|
1332
1327
|
if isinstance(x, float):
|
1333
1328
|
y = cast("float", y)
|
1334
1329
|
return _sort_iterable_cmp_floats(x, y)
|
@@ -1371,30 +1366,6 @@ class SortIterableError(Exception):
|
|
1371
1366
|
return f"Unable to sort {get_repr(self.x)} and {get_repr(self.y)}"
|
1372
1367
|
|
1373
1368
|
|
1374
|
-
def _sort_iterable_cmp_datetimes(x: dt.datetime, y: dt.datetime, /) -> Sign:
|
1375
|
-
"""Compare two datetimes."""
|
1376
|
-
match x.tzinfo, y.tzinfo:
|
1377
|
-
case None, None:
|
1378
|
-
return cast("Sign", (x > y) - (x < y))
|
1379
|
-
case dt.tzinfo(), None:
|
1380
|
-
return 1
|
1381
|
-
case None, dt.tzinfo():
|
1382
|
-
return -1
|
1383
|
-
case dt.tzinfo(), dt.tzinfo():
|
1384
|
-
x_utc = x.astimezone(tz=UTC)
|
1385
|
-
y_utc = y.astimezone(tz=UTC)
|
1386
|
-
result = cast("Sign", (x_utc > y_utc) - (x_utc < y_utc))
|
1387
|
-
if result != 0:
|
1388
|
-
return result
|
1389
|
-
x_time_zone = ensure_not_none(ensure_not_none(x.tzinfo).tzname(x))
|
1390
|
-
y_time_zone = ensure_not_none(ensure_not_none(y.tzinfo).tzname(y))
|
1391
|
-
return cast(
|
1392
|
-
"Sign", (x_time_zone > y_time_zone) - (x_time_zone < y_time_zone)
|
1393
|
-
)
|
1394
|
-
case _ as never:
|
1395
|
-
assert_never(never)
|
1396
|
-
|
1397
|
-
|
1398
1369
|
def _sort_iterable_cmp_floats(x: float, y: float, /) -> Sign:
|
1399
1370
|
"""Compare two floats."""
|
1400
1371
|
x_nan, y_nan = map(isnan, [x, y])
|
utilities/operator.py
CHANGED
@@ -1,23 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import datetime as dt
|
4
3
|
from collections.abc import Callable, Mapping, Sequence
|
5
4
|
from collections.abc import Set as AbstractSet
|
6
5
|
from dataclasses import asdict, dataclass
|
7
6
|
from typing import TYPE_CHECKING, Any, TypeVar, cast, override
|
8
7
|
|
9
8
|
import utilities.math
|
10
|
-
from utilities.datetime import (
|
11
|
-
AreEqualDatesOrDateTimesError,
|
12
|
-
AreEqualDateTimesError,
|
13
|
-
are_equal_dates_or_datetimes,
|
14
|
-
)
|
15
9
|
from utilities.functions import is_dataclass_instance
|
16
10
|
from utilities.iterables import SortIterableError, sort_iterable
|
17
11
|
from utilities.reprlib import get_repr
|
18
12
|
|
19
13
|
if TYPE_CHECKING:
|
20
|
-
from utilities.types import Dataclass,
|
14
|
+
from utilities.types import Dataclass, Number
|
21
15
|
|
22
16
|
_T = TypeVar("_T")
|
23
17
|
|
@@ -51,12 +45,6 @@ def is_equal(
|
|
51
45
|
if isinstance(x, str): # else Sequence
|
52
46
|
y = cast("str", y)
|
53
47
|
return x == y
|
54
|
-
if isinstance(x, dt.date | dt.datetime):
|
55
|
-
y = cast("DateOrDateTime", y)
|
56
|
-
try:
|
57
|
-
return are_equal_dates_or_datetimes(x, y)
|
58
|
-
except (AreEqualDateTimesError, AreEqualDatesOrDateTimesError):
|
59
|
-
return False
|
60
48
|
if is_dataclass_instance(x):
|
61
49
|
y = cast("Dataclass", y)
|
62
50
|
x_values = asdict(x)
|
@@ -80,8 +68,26 @@ def is_equal(
|
|
80
68
|
try:
|
81
69
|
x_sorted = sort_iterable(x)
|
82
70
|
y_sorted = sort_iterable(y)
|
83
|
-
except SortIterableError
|
84
|
-
|
71
|
+
except SortIterableError:
|
72
|
+
x_in_y = all(
|
73
|
+
any(
|
74
|
+
is_equal(
|
75
|
+
x_i, y_i, rel_tol=rel_tol, abs_tol=abs_tol, extra=extra
|
76
|
+
)
|
77
|
+
for y_i in y
|
78
|
+
)
|
79
|
+
for x_i in x
|
80
|
+
)
|
81
|
+
y_in_x = all(
|
82
|
+
any(
|
83
|
+
is_equal(
|
84
|
+
x_i, y_i, rel_tol=rel_tol, abs_tol=abs_tol, extra=extra
|
85
|
+
)
|
86
|
+
for x_i in x
|
87
|
+
)
|
88
|
+
for y_i in y
|
89
|
+
)
|
90
|
+
return x_in_y and y_in_x
|
85
91
|
return is_equal(x_sorted, y_sorted, rel_tol=rel_tol, abs_tol=abs_tol)
|
86
92
|
if isinstance(x, Sequence):
|
87
93
|
y = cast("Sequence[Any]", y)
|
utilities/orjson.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import datetime as dt
|
4
3
|
import re
|
5
4
|
from collections.abc import Callable, Iterable, Mapping, Sequence
|
6
5
|
from contextlib import suppress
|
@@ -23,7 +22,15 @@ from orjson import (
|
|
23
22
|
dumps,
|
24
23
|
loads,
|
25
24
|
)
|
26
|
-
from whenever import
|
25
|
+
from whenever import (
|
26
|
+
Date,
|
27
|
+
DateDelta,
|
28
|
+
DateTimeDelta,
|
29
|
+
PlainDateTime,
|
30
|
+
Time,
|
31
|
+
TimeDelta,
|
32
|
+
ZonedDateTime,
|
33
|
+
)
|
27
34
|
|
28
35
|
from utilities.concurrent import concurrent_map
|
29
36
|
from utilities.dataclasses import dataclass_to_dict
|
@@ -39,27 +46,13 @@ from utilities.logging import get_logging_level_number
|
|
39
46
|
from utilities.math import MAX_INT64, MIN_INT64
|
40
47
|
from utilities.types import Dataclass, LogLevel, MaybeIterable, PathLike, StrMapping
|
41
48
|
from utilities.tzlocal import LOCAL_TIME_ZONE
|
42
|
-
from utilities.uuid import UUID_PATTERN
|
43
49
|
from utilities.version import Version, parse_version
|
44
|
-
from utilities.whenever import (
|
45
|
-
parse_date,
|
46
|
-
parse_plain_datetime,
|
47
|
-
parse_time,
|
48
|
-
parse_timedelta,
|
49
|
-
parse_zoned_datetime,
|
50
|
-
serialize_date,
|
51
|
-
serialize_datetime,
|
52
|
-
serialize_time,
|
53
|
-
serialize_timedelta,
|
54
|
-
)
|
55
50
|
from utilities.whenever2 import from_timestamp
|
56
51
|
|
57
52
|
if TYPE_CHECKING:
|
58
53
|
from collections.abc import Set as AbstractSet
|
59
54
|
from logging import _FormatStyle
|
60
55
|
|
61
|
-
from whenever import Date
|
62
|
-
|
63
56
|
from utilities.types import Parallelism
|
64
57
|
|
65
58
|
|
@@ -70,26 +63,25 @@ if TYPE_CHECKING:
|
|
70
63
|
class _Prefixes(Enum):
|
71
64
|
dataclass = "dc"
|
72
65
|
date = "d"
|
73
|
-
|
66
|
+
date_delta = "dd"
|
67
|
+
date_time_delta = "D"
|
74
68
|
enum = "e"
|
75
|
-
exception_class = "
|
76
|
-
exception_instance = "
|
69
|
+
exception_class = "Ex"
|
70
|
+
exception_instance = "ex"
|
77
71
|
float_ = "fl"
|
78
72
|
frozenset_ = "fr"
|
79
73
|
list_ = "l"
|
80
|
-
nan = "nan"
|
81
74
|
none = "none"
|
82
75
|
path = "p"
|
83
|
-
|
84
|
-
neg_inf = "neg_inf"
|
76
|
+
plain_date_time = "pd"
|
85
77
|
set_ = "s"
|
86
|
-
|
87
|
-
|
78
|
+
time = "ti"
|
79
|
+
time_delta = "td"
|
88
80
|
tuple_ = "tu"
|
89
81
|
unserializable = "un"
|
90
82
|
uuid = "uu"
|
91
83
|
version = "v"
|
92
|
-
|
84
|
+
zoned_date_time = "zd"
|
93
85
|
|
94
86
|
|
95
87
|
type _DataclassHook = Callable[[type[Dataclass], StrMapping], StrMapping]
|
@@ -160,14 +152,12 @@ def _pre_process(
|
|
160
152
|
# singletons
|
161
153
|
case None:
|
162
154
|
return f"[{_Prefixes.none.value}]"
|
163
|
-
case
|
164
|
-
return f"[{_Prefixes.
|
165
|
-
case
|
166
|
-
return f"[{_Prefixes.
|
167
|
-
case
|
168
|
-
return f"[{_Prefixes.
|
169
|
-
case dt.timedelta() as timedelta:
|
170
|
-
return f"[{_Prefixes.timedelta.value}]{serialize_timedelta(timedelta)}"
|
155
|
+
case Date() as date:
|
156
|
+
return f"[{_Prefixes.date.value}]{date}"
|
157
|
+
case DateDelta() as date:
|
158
|
+
return f"[{_Prefixes.date_delta.value}]{date}"
|
159
|
+
case DateTimeDelta() as date:
|
160
|
+
return f"[{_Prefixes.date_time_delta.value}]{date}"
|
171
161
|
case Exception() as error_:
|
172
162
|
return {
|
173
163
|
f"[{_Prefixes.exception_instance.value}|{type(error_).__qualname__}]": pre(
|
@@ -182,18 +172,24 @@ def _pre_process(
|
|
182
172
|
if MIN_INT64 <= int_ <= MAX_INT64:
|
183
173
|
return int_
|
184
174
|
raise _SerializeIntegerError(obj=int_)
|
185
|
-
case UUID() as uuid:
|
186
|
-
return f"[{_Prefixes.uuid.value}]{uuid}"
|
187
175
|
case Path() as path:
|
188
176
|
return f"[{_Prefixes.path.value}]{path!s}"
|
177
|
+
case PlainDateTime() as datetime:
|
178
|
+
return f"[{_Prefixes.plain_date_time.value}]{datetime}"
|
189
179
|
case str() as str_:
|
190
180
|
return str_
|
181
|
+
case Time() as time:
|
182
|
+
return f"[{_Prefixes.time.value}]{time}"
|
183
|
+
case TimeDelta() as time_delta:
|
184
|
+
return f"[{_Prefixes.time_delta.value}]{time_delta}"
|
191
185
|
case type() as error_cls if issubclass(error_cls, Exception):
|
192
186
|
return f"[{_Prefixes.exception_class.value}|{error_cls.__qualname__}]"
|
187
|
+
case UUID() as uuid:
|
188
|
+
return f"[{_Prefixes.uuid.value}]{uuid}"
|
193
189
|
case Version() as version:
|
194
190
|
return f"[{_Prefixes.version.value}]{version!s}"
|
195
191
|
case ZonedDateTime() as datetime:
|
196
|
-
return f"[{_Prefixes.
|
192
|
+
return f"[{_Prefixes.zoned_date_time.value}]{datetime}"
|
197
193
|
# contains
|
198
194
|
case Dataclass() as dataclass:
|
199
195
|
asdict = dataclass_to_dict(
|
@@ -338,51 +334,36 @@ def deserialize(
|
|
338
334
|
)
|
339
335
|
|
340
336
|
|
341
|
-
_NONE_PATTERN = re.compile(r"^\[" + _Prefixes.none.value + r"\]$")
|
342
|
-
_LOCAL_DATETIME_PATTERN = re.compile(
|
343
|
-
r"^\["
|
344
|
-
+ _Prefixes.datetime.value
|
345
|
-
+ r"\](\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?)$"
|
346
|
-
)
|
347
|
-
_UUID_PATTERN = re.compile(r"^\[" + _Prefixes.uuid.value + r"\](" + UUID_PATTERN + ")$")
|
348
|
-
_ZONED_DATETIME_PATTERN = re.compile(
|
349
|
-
r"^\["
|
350
|
-
+ _Prefixes.datetime.value
|
351
|
-
+ r"\](\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?[\+\-]\d{2}:\d{2}(?::\d{2})?\[(?!(?:dt\.)).+?\])$"
|
352
|
-
)
|
353
|
-
_ZONED_DATETIME_PATTERN2 = re.compile(
|
354
|
-
r"^\["
|
355
|
-
+ _Prefixes.zoned_datetime.value
|
356
|
-
+ r"\](\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?[\+\-]\d{2}:\d{2}(?::\d{2})?\[(?!(?:dt\.)).+?\])$"
|
357
|
-
)
|
358
|
-
|
359
|
-
|
360
|
-
def _make_unit_pattern(prefix: _Prefixes, /) -> Pattern[str]:
|
361
|
-
return re.compile(r"^\[" + prefix.value + r"\](.+)$")
|
362
|
-
|
363
|
-
|
364
337
|
(
|
365
338
|
_DATE_PATTERN,
|
339
|
+
_DATE_DELTA_PATTERN,
|
340
|
+
_DATE_TIME_DELTA_PATTERN,
|
366
341
|
_FLOAT_PATTERN,
|
342
|
+
_NONE_PATTERN,
|
367
343
|
_PATH_PATTERN,
|
344
|
+
_PLAIN_DATE_TIME_PATTERN,
|
368
345
|
_TIME_PATTERN,
|
369
|
-
|
346
|
+
_TIME_DELTA_PATTERN,
|
347
|
+
_UUID_PATTERN,
|
370
348
|
_VERSION_PATTERN,
|
371
|
-
|
372
|
-
|
373
|
-
[
|
349
|
+
_ZONED_DATE_TIME_PATTERN,
|
350
|
+
) = [
|
351
|
+
re.compile(r"^\[" + p.value + r"\](" + ".*" + ")$")
|
352
|
+
for p in [
|
374
353
|
_Prefixes.date,
|
354
|
+
_Prefixes.date_delta,
|
355
|
+
_Prefixes.date_time_delta,
|
375
356
|
_Prefixes.float_,
|
357
|
+
_Prefixes.none,
|
376
358
|
_Prefixes.path,
|
359
|
+
_Prefixes.plain_date_time,
|
377
360
|
_Prefixes.time,
|
378
|
-
_Prefixes.
|
361
|
+
_Prefixes.time_delta,
|
362
|
+
_Prefixes.uuid,
|
379
363
|
_Prefixes.version,
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
def _make_container_pattern(prefix: _Prefixes, /) -> Pattern[str]:
|
385
|
-
return re.compile(r"^\[" + prefix.value + r"(?:\|(.+))?\]$")
|
364
|
+
_Prefixes.zoned_date_time,
|
365
|
+
]
|
366
|
+
]
|
386
367
|
|
387
368
|
|
388
369
|
(
|
@@ -394,9 +375,9 @@ def _make_container_pattern(prefix: _Prefixes, /) -> Pattern[str]:
|
|
394
375
|
_LIST_PATTERN,
|
395
376
|
_SET_PATTERN,
|
396
377
|
_TUPLE_PATTERN,
|
397
|
-
) =
|
398
|
-
|
399
|
-
[
|
378
|
+
) = [
|
379
|
+
re.compile(r"^\[" + p.value + r"(?:\|(.+))?\]$")
|
380
|
+
for p in [
|
400
381
|
_Prefixes.dataclass,
|
401
382
|
_Prefixes.enum,
|
402
383
|
_Prefixes.exception_class,
|
@@ -405,8 +386,8 @@ def _make_container_pattern(prefix: _Prefixes, /) -> Pattern[str]:
|
|
405
386
|
_Prefixes.list_,
|
406
387
|
_Prefixes.set_,
|
407
388
|
_Prefixes.tuple_,
|
408
|
-
]
|
409
|
-
|
389
|
+
]
|
390
|
+
]
|
410
391
|
|
411
392
|
|
412
393
|
def _object_hook(
|
@@ -425,24 +406,26 @@ def _object_hook(
|
|
425
406
|
if match := _NONE_PATTERN.search(text):
|
426
407
|
return None
|
427
408
|
if match := _DATE_PATTERN.search(text):
|
428
|
-
return
|
409
|
+
return Date.parse_common_iso(match.group(1))
|
410
|
+
if match := _DATE_DELTA_PATTERN.search(text):
|
411
|
+
return DateDelta.parse_common_iso(match.group(1))
|
412
|
+
if match := _DATE_TIME_DELTA_PATTERN.search(text):
|
413
|
+
return DateTimeDelta.parse_common_iso(match.group(1))
|
429
414
|
if match := _FLOAT_PATTERN.search(text):
|
430
415
|
return float(match.group(1))
|
431
|
-
if match := _LOCAL_DATETIME_PATTERN.search(text):
|
432
|
-
return parse_plain_datetime(match.group(1))
|
433
416
|
if match := _PATH_PATTERN.search(text):
|
434
417
|
return Path(match.group(1))
|
418
|
+
if match := _PLAIN_DATE_TIME_PATTERN.search(text):
|
419
|
+
return PlainDateTime.parse_common_iso(match.group(1))
|
435
420
|
if match := _TIME_PATTERN.search(text):
|
436
|
-
return
|
437
|
-
if match :=
|
438
|
-
return
|
421
|
+
return Time.parse_common_iso(match.group(1))
|
422
|
+
if match := _TIME_DELTA_PATTERN.search(text):
|
423
|
+
return TimeDelta.parse_common_iso(match.group(1))
|
439
424
|
if match := _UUID_PATTERN.search(text):
|
440
425
|
return UUID(match.group(1))
|
441
426
|
if match := _VERSION_PATTERN.search(text):
|
442
427
|
return parse_version(match.group(1))
|
443
|
-
if match :=
|
444
|
-
return parse_zoned_datetime(match.group(1))
|
445
|
-
if match := _ZONED_DATETIME_PATTERN2.search(text):
|
428
|
+
if match := _ZONED_DATE_TIME_PATTERN.search(text):
|
446
429
|
return ZonedDateTime.parse_common_iso(match.group(1))
|
447
430
|
if (
|
448
431
|
exc_class := _object_hook_exception_class(
|
utilities/parse.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import datetime as dt
|
4
3
|
from contextlib import suppress
|
5
4
|
from dataclasses import dataclass
|
6
5
|
from enum import Enum
|
@@ -9,6 +8,16 @@ from re import DOTALL
|
|
9
8
|
from types import NoneType
|
10
9
|
from typing import TYPE_CHECKING, Any, override
|
11
10
|
|
11
|
+
from whenever import (
|
12
|
+
Date,
|
13
|
+
DateDelta,
|
14
|
+
DateTimeDelta,
|
15
|
+
PlainDateTime,
|
16
|
+
Time,
|
17
|
+
TimeDelta,
|
18
|
+
ZonedDateTime,
|
19
|
+
)
|
20
|
+
|
12
21
|
from utilities.enum import ParseEnumError, parse_enum
|
13
22
|
from utilities.iterables import OneEmptyError, OneNonUniqueError, one, one_str
|
14
23
|
from utilities.math import ParseNumberError, parse_number
|
@@ -26,7 +35,7 @@ from utilities.text import (
|
|
26
35
|
split_key_value_pairs,
|
27
36
|
split_str,
|
28
37
|
)
|
29
|
-
from utilities.types import
|
38
|
+
from utilities.types import Number, ParseObjectExtra, SerializeObjectExtra
|
30
39
|
from utilities.typing import (
|
31
40
|
get_args,
|
32
41
|
is_dict_type,
|
@@ -182,6 +191,14 @@ def _parse_object_type(
|
|
182
191
|
return parse_enum(text, cls, case_sensitive=case_sensitive)
|
183
192
|
except ParseEnumError:
|
184
193
|
raise _ParseObjectParseError(type_=cls, text=text) from None
|
194
|
+
if issubclass(
|
195
|
+
cls,
|
196
|
+
(Date, DateDelta, DateTimeDelta, PlainDateTime, Time, TimeDelta, ZonedDateTime),
|
197
|
+
):
|
198
|
+
try:
|
199
|
+
return cls.parse_common_iso(text)
|
200
|
+
except ValueError:
|
201
|
+
raise _ParseObjectParseError(type_=cls, text=text) from None
|
185
202
|
if issubclass(cls, Path):
|
186
203
|
return Path(text).expanduser()
|
187
204
|
if issubclass(cls, Sentinel):
|
@@ -194,34 +211,6 @@ def _parse_object_type(
|
|
194
211
|
return parse_version(text)
|
195
212
|
except ParseVersionError:
|
196
213
|
raise _ParseObjectParseError(type_=cls, text=text) from None
|
197
|
-
if is_subclass_gen(cls, dt.date):
|
198
|
-
from utilities.whenever import ParseDateError, parse_date
|
199
|
-
|
200
|
-
try:
|
201
|
-
return parse_date(text)
|
202
|
-
except ParseDateError:
|
203
|
-
raise _ParseObjectParseError(type_=cls, text=text) from None
|
204
|
-
if is_subclass_gen(cls, dt.datetime):
|
205
|
-
from utilities.whenever import ParseDateTimeError, parse_datetime
|
206
|
-
|
207
|
-
try:
|
208
|
-
return parse_datetime(text)
|
209
|
-
except ParseDateTimeError:
|
210
|
-
raise _ParseObjectParseError(type_=cls, text=text) from None
|
211
|
-
if issubclass(cls, dt.time):
|
212
|
-
from utilities.whenever import ParseTimeError, parse_time
|
213
|
-
|
214
|
-
try:
|
215
|
-
return parse_time(text)
|
216
|
-
except ParseTimeError:
|
217
|
-
raise _ParseObjectParseError(type_=cls, text=text) from None
|
218
|
-
if issubclass(cls, dt.timedelta):
|
219
|
-
from utilities.whenever import ParseTimedeltaError, parse_timedelta
|
220
|
-
|
221
|
-
try:
|
222
|
-
return parse_timedelta(text)
|
223
|
-
except ParseTimedeltaError:
|
224
|
-
raise _ParseObjectParseError(type_=cls, text=text) from None
|
225
214
|
raise _ParseObjectParseError(type_=cls, text=text)
|
226
215
|
|
227
216
|
|
@@ -374,13 +363,6 @@ def _parse_object_union_type(type_: Any, text: str, /) -> Any:
|
|
374
363
|
return parse_number(text)
|
375
364
|
except ParseNumberError:
|
376
365
|
raise _ParseObjectParseError(type_=type_, text=text) from None
|
377
|
-
if type_ is Duration:
|
378
|
-
from utilities.whenever import ParseDurationError, parse_duration
|
379
|
-
|
380
|
-
try:
|
381
|
-
return parse_duration(text)
|
382
|
-
except ParseDurationError:
|
383
|
-
raise _ParseObjectParseError(type_=type_, text=text) from None
|
384
366
|
raise _ParseObjectParseError(type_=type_, text=text) from None
|
385
367
|
|
386
368
|
|
@@ -464,22 +446,11 @@ def serialize_object(
|
|
464
446
|
obj, bool | int | float | str | Path | Sentinel | Version
|
465
447
|
):
|
466
448
|
return str(obj)
|
467
|
-
if
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
from utilities.whenever import serialize_datetime
|
473
|
-
|
474
|
-
return serialize_datetime(obj)
|
475
|
-
if isinstance(obj, dt.time):
|
476
|
-
from utilities.whenever import serialize_time
|
477
|
-
|
478
|
-
return serialize_time(obj)
|
479
|
-
if isinstance(obj, dt.timedelta):
|
480
|
-
from utilities.whenever import serialize_timedelta
|
481
|
-
|
482
|
-
return serialize_timedelta(obj)
|
449
|
+
if isinstance(
|
450
|
+
obj,
|
451
|
+
(Date, DateDelta, DateTimeDelta, PlainDateTime, Time, TimeDelta, ZonedDateTime),
|
452
|
+
):
|
453
|
+
return obj.format_common_iso()
|
483
454
|
if isinstance(obj, Enum):
|
484
455
|
return obj.name
|
485
456
|
if isinstance(obj, dict):
|
utilities/whenever.py
CHANGED
@@ -1,27 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import datetime as dt
|
4
|
-
import re
|
5
4
|
from contextlib import suppress
|
6
5
|
from dataclasses import dataclass
|
7
6
|
from typing import TYPE_CHECKING, override
|
8
7
|
|
9
|
-
from whenever import
|
10
|
-
Date,
|
11
|
-
DateTimeDelta,
|
12
|
-
PlainDateTime,
|
13
|
-
Time,
|
14
|
-
TimeZoneNotFoundError,
|
15
|
-
ZonedDateTime,
|
16
|
-
)
|
8
|
+
from whenever import DateTimeDelta, TimeZoneNotFoundError, ZonedDateTime
|
17
9
|
|
18
10
|
from utilities.datetime import (
|
19
11
|
_MICROSECONDS_PER_DAY,
|
20
12
|
_MICROSECONDS_PER_SECOND,
|
21
13
|
ZERO_TIME,
|
22
|
-
check_date_not_datetime,
|
23
14
|
datetime_duration_to_microseconds,
|
24
|
-
parse_two_digit_year,
|
25
15
|
)
|
26
16
|
from utilities.math import ParseNumberError, parse_number
|
27
17
|
from utilities.re import (
|
@@ -35,7 +25,6 @@ from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
|
|
35
25
|
if TYPE_CHECKING:
|
36
26
|
from utilities.types import Duration
|
37
27
|
|
38
|
-
|
39
28
|
MAX_SERIALIZABLE_TIMEDELTA = dt.timedelta(days=3652060, microseconds=-1)
|
40
29
|
MIN_SERIALIZABLE_TIMEDELTA = -MAX_SERIALIZABLE_TIMEDELTA
|
41
30
|
|
@@ -84,56 +73,6 @@ class _CheckValidZonedDateTimeUnequalError(CheckValidZonedDateTimeError):
|
|
84
73
|
##
|
85
74
|
|
86
75
|
|
87
|
-
_PARSE_DATE_YYMMDD_REGEX = re.compile(r"^(\d{2})(\d{2})(\d{2})$")
|
88
|
-
|
89
|
-
|
90
|
-
def parse_date(date: str, /) -> dt.date:
|
91
|
-
"""Parse a string into a date."""
|
92
|
-
try:
|
93
|
-
w_date = Date.parse_common_iso(date)
|
94
|
-
except ValueError:
|
95
|
-
try:
|
96
|
-
((year2, month, day),) = _PARSE_DATE_YYMMDD_REGEX.findall(date)
|
97
|
-
except ValueError:
|
98
|
-
raise ParseDateError(date=date) from None
|
99
|
-
year = parse_two_digit_year(year2)
|
100
|
-
return dt.date(year=int(year), month=int(month), day=int(day))
|
101
|
-
return w_date.py_date()
|
102
|
-
|
103
|
-
|
104
|
-
@dataclass(kw_only=True, slots=True)
|
105
|
-
class ParseDateError(Exception):
|
106
|
-
date: str
|
107
|
-
|
108
|
-
@override
|
109
|
-
def __str__(self) -> str:
|
110
|
-
return f"Unable to parse date; got {self.date!r}"
|
111
|
-
|
112
|
-
|
113
|
-
##
|
114
|
-
|
115
|
-
|
116
|
-
def parse_datetime(datetime: str, /) -> dt.datetime:
|
117
|
-
"""Parse a string into a datetime."""
|
118
|
-
with suppress(ParsePlainDateTimeError):
|
119
|
-
return parse_plain_datetime(datetime)
|
120
|
-
with suppress(ParseZonedDateTimeError):
|
121
|
-
return parse_zoned_datetime(datetime)
|
122
|
-
raise ParseDateTimeError(datetime=datetime) from None
|
123
|
-
|
124
|
-
|
125
|
-
@dataclass(kw_only=True, slots=True)
|
126
|
-
class ParseDateTimeError(Exception):
|
127
|
-
datetime: str
|
128
|
-
|
129
|
-
@override
|
130
|
-
def __str__(self) -> str:
|
131
|
-
return f"Unable to parse datetime; got {self.datetime!r}"
|
132
|
-
|
133
|
-
|
134
|
-
##
|
135
|
-
|
136
|
-
|
137
76
|
def parse_duration(duration: str, /) -> Duration:
|
138
77
|
"""Parse a string into a Duration."""
|
139
78
|
with suppress(ParseNumberError):
|
@@ -156,48 +95,6 @@ class ParseDurationError(Exception):
|
|
156
95
|
##
|
157
96
|
|
158
97
|
|
159
|
-
def parse_plain_datetime(datetime: str, /) -> dt.datetime:
|
160
|
-
"""Parse a string into a plain datetime."""
|
161
|
-
try:
|
162
|
-
ldt = PlainDateTime.parse_common_iso(datetime)
|
163
|
-
except ValueError:
|
164
|
-
raise ParsePlainDateTimeError(datetime=datetime) from None
|
165
|
-
return ldt.py_datetime()
|
166
|
-
|
167
|
-
|
168
|
-
@dataclass(kw_only=True, slots=True)
|
169
|
-
class ParsePlainDateTimeError(Exception):
|
170
|
-
datetime: str
|
171
|
-
|
172
|
-
@override
|
173
|
-
def __str__(self) -> str:
|
174
|
-
return f"Unable to parse plain datetime; got {self.datetime!r}"
|
175
|
-
|
176
|
-
|
177
|
-
##
|
178
|
-
|
179
|
-
|
180
|
-
def parse_time(time: str, /) -> dt.time:
|
181
|
-
"""Parse a string into a time."""
|
182
|
-
try:
|
183
|
-
w_time = Time.parse_common_iso(time)
|
184
|
-
except ValueError:
|
185
|
-
raise ParseTimeError(time=time) from None
|
186
|
-
return w_time.py_time()
|
187
|
-
|
188
|
-
|
189
|
-
@dataclass(kw_only=True, slots=True)
|
190
|
-
class ParseTimeError(Exception):
|
191
|
-
time: str
|
192
|
-
|
193
|
-
@override
|
194
|
-
def __str__(self) -> str:
|
195
|
-
return f"Unable to parse time; got {self.time!r}"
|
196
|
-
|
197
|
-
|
198
|
-
##
|
199
|
-
|
200
|
-
|
201
98
|
def parse_timedelta(timedelta: str, /) -> dt.timedelta:
|
202
99
|
"""Parse a string into a timedelta."""
|
203
100
|
with suppress(ExtractGroupError):
|
@@ -245,47 +142,6 @@ class _ParseTimedeltaNanosecondError(ParseTimedeltaError):
|
|
245
142
|
##
|
246
143
|
|
247
144
|
|
248
|
-
def parse_zoned_datetime(datetime: str, /) -> dt.datetime:
|
249
|
-
"""Parse a string into a zoned datetime."""
|
250
|
-
try:
|
251
|
-
zdt = ZonedDateTime.parse_common_iso(datetime)
|
252
|
-
except ValueError:
|
253
|
-
raise ParseZonedDateTimeError(datetime=datetime) from None
|
254
|
-
return zdt.py_datetime()
|
255
|
-
|
256
|
-
|
257
|
-
@dataclass(kw_only=True, slots=True)
|
258
|
-
class ParseZonedDateTimeError(Exception):
|
259
|
-
datetime: str
|
260
|
-
|
261
|
-
@override
|
262
|
-
def __str__(self) -> str:
|
263
|
-
return f"Unable to parse zoned datetime; got {self.datetime!r}"
|
264
|
-
|
265
|
-
|
266
|
-
##
|
267
|
-
|
268
|
-
|
269
|
-
def serialize_date(date: dt.date, /) -> str:
|
270
|
-
"""Serialize a date."""
|
271
|
-
check_date_not_datetime(date)
|
272
|
-
return Date.from_py_date(date).format_common_iso()
|
273
|
-
|
274
|
-
|
275
|
-
##
|
276
|
-
|
277
|
-
|
278
|
-
def serialize_datetime(datetime: dt.datetime, /) -> str:
|
279
|
-
"""Serialize a datetime."""
|
280
|
-
try:
|
281
|
-
return serialize_plain_datetime(datetime)
|
282
|
-
except SerializePlainDateTimeError:
|
283
|
-
return serialize_zoned_datetime(datetime)
|
284
|
-
|
285
|
-
|
286
|
-
##
|
287
|
-
|
288
|
-
|
289
145
|
def serialize_duration(duration: Duration, /) -> str:
|
290
146
|
"""Serialize a duration."""
|
291
147
|
if isinstance(duration, int | float):
|
@@ -308,35 +164,6 @@ class SerializeDurationError(Exception):
|
|
308
164
|
##
|
309
165
|
|
310
166
|
|
311
|
-
def serialize_plain_datetime(datetime: dt.datetime, /) -> str:
|
312
|
-
"""Serialize a plain datetime."""
|
313
|
-
try:
|
314
|
-
pdt = PlainDateTime.from_py_datetime(datetime)
|
315
|
-
except ValueError:
|
316
|
-
raise SerializePlainDateTimeError(datetime=datetime) from None
|
317
|
-
return pdt.format_common_iso()
|
318
|
-
|
319
|
-
|
320
|
-
@dataclass(kw_only=True, slots=True)
|
321
|
-
class SerializePlainDateTimeError(Exception):
|
322
|
-
datetime: dt.datetime
|
323
|
-
|
324
|
-
@override
|
325
|
-
def __str__(self) -> str:
|
326
|
-
return f"Unable to serialize plain datetime; got {self.datetime}"
|
327
|
-
|
328
|
-
|
329
|
-
##
|
330
|
-
|
331
|
-
|
332
|
-
def serialize_time(time: dt.time, /) -> str:
|
333
|
-
"""Serialize a time."""
|
334
|
-
return Time.from_py_time(time).format_common_iso()
|
335
|
-
|
336
|
-
|
337
|
-
##
|
338
|
-
|
339
|
-
|
340
167
|
def serialize_timedelta(timedelta: dt.timedelta, /) -> str:
|
341
168
|
"""Serialize a timedelta."""
|
342
169
|
try:
|
@@ -358,31 +185,6 @@ class SerializeTimeDeltaError(Exception):
|
|
358
185
|
##
|
359
186
|
|
360
187
|
|
361
|
-
def serialize_zoned_datetime(datetime: dt.datetime, /) -> str:
|
362
|
-
"""Serialize a zoned datetime."""
|
363
|
-
if datetime.tzinfo is dt.UTC:
|
364
|
-
return serialize_zoned_datetime( # skipif-ci-and-windows
|
365
|
-
datetime.replace(tzinfo=UTC)
|
366
|
-
)
|
367
|
-
try:
|
368
|
-
zdt = ZonedDateTime.from_py_datetime(datetime)
|
369
|
-
except ValueError:
|
370
|
-
raise SerializeZonedDateTimeError(datetime=datetime) from None
|
371
|
-
return zdt.format_common_iso()
|
372
|
-
|
373
|
-
|
374
|
-
@dataclass(kw_only=True, slots=True)
|
375
|
-
class SerializeZonedDateTimeError(Exception):
|
376
|
-
datetime: dt.datetime
|
377
|
-
|
378
|
-
@override
|
379
|
-
def __str__(self) -> str:
|
380
|
-
return f"Unable to serialize zoned datetime; got {self.datetime}"
|
381
|
-
|
382
|
-
|
383
|
-
##
|
384
|
-
|
385
|
-
|
386
188
|
def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
|
387
189
|
"""Serialize a timedelta."""
|
388
190
|
total_microseconds = datetime_duration_to_microseconds(timedelta)
|
@@ -415,30 +217,14 @@ __all__ = [
|
|
415
217
|
"MAX_SERIALIZABLE_TIMEDELTA",
|
416
218
|
"MIN_SERIALIZABLE_TIMEDELTA",
|
417
219
|
"CheckValidZonedDateTimeError",
|
418
|
-
"ParseDateError",
|
419
|
-
"ParseDateTimeError",
|
420
220
|
"ParseDurationError",
|
421
|
-
"ParsePlainDateTimeError",
|
422
|
-
"ParseTimeError",
|
423
221
|
"ParseTimedeltaError",
|
424
|
-
"ParseZonedDateTimeError",
|
425
222
|
"SerializeDurationError",
|
426
|
-
"SerializePlainDateTimeError",
|
427
223
|
"SerializeTimeDeltaError",
|
428
|
-
"SerializeZonedDateTimeError",
|
429
224
|
"check_valid_zoned_datetime",
|
430
|
-
"
|
431
|
-
"parse_datetime",
|
225
|
+
"check_valid_zoned_datetime",
|
432
226
|
"parse_duration",
|
433
|
-
"parse_plain_datetime",
|
434
|
-
"parse_time",
|
435
227
|
"parse_timedelta",
|
436
|
-
"parse_zoned_datetime",
|
437
|
-
"serialize_date",
|
438
|
-
"serialize_datetime",
|
439
228
|
"serialize_duration",
|
440
|
-
"serialize_plain_datetime",
|
441
|
-
"serialize_time",
|
442
229
|
"serialize_timedelta",
|
443
|
-
"serialize_zoned_datetime",
|
444
230
|
]
|
File without changes
|
File without changes
|