dycw-utilities 0.117.1__py3-none-any.whl → 0.119.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dycw_utilities-0.117.1.dist-info → dycw_utilities-0.119.0.dist-info}/METADATA +27 -27
- {dycw_utilities-0.117.1.dist-info → dycw_utilities-0.119.0.dist-info}/RECORD +17 -17
- utilities/__init__.py +1 -1
- utilities/asyncio.py +2 -224
- utilities/click.py +19 -19
- utilities/datetime.py +5 -5
- utilities/fastapi.py +3 -8
- utilities/hypothesis.py +44 -44
- utilities/orjson.py +2 -2
- utilities/period.py +2 -2
- utilities/redis.py +1 -18
- utilities/slack_sdk.py +2 -68
- utilities/sqlalchemy.py +1 -44
- utilities/whenever.py +66 -104
- utilities/zoneinfo.py +3 -3
- {dycw_utilities-0.117.1.dist-info → dycw_utilities-0.119.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.117.1.dist-info → dycw_utilities-0.119.0.dist-info}/licenses/LICENSE +0 -0
utilities/whenever.py
CHANGED
@@ -5,9 +5,15 @@ import re
|
|
5
5
|
from contextlib import suppress
|
6
6
|
from dataclasses import dataclass
|
7
7
|
from typing import TYPE_CHECKING, override
|
8
|
-
from zoneinfo import ZoneInfo
|
9
8
|
|
10
|
-
from whenever import
|
9
|
+
from whenever import (
|
10
|
+
Date,
|
11
|
+
DateTimeDelta,
|
12
|
+
PlainDateTime,
|
13
|
+
Time,
|
14
|
+
TimeZoneNotFoundError,
|
15
|
+
ZonedDateTime,
|
16
|
+
)
|
11
17
|
|
12
18
|
from utilities.datetime import (
|
13
19
|
_MICROSECONDS_PER_DAY,
|
@@ -37,7 +43,7 @@ if TYPE_CHECKING:
|
|
37
43
|
)
|
38
44
|
|
39
45
|
|
40
|
-
MAX_SERIALIZABLE_TIMEDELTA = dt.timedelta(days=
|
46
|
+
MAX_SERIALIZABLE_TIMEDELTA = dt.timedelta(days=3652060, microseconds=-1)
|
41
47
|
MIN_SERIALIZABLE_TIMEDELTA = -MAX_SERIALIZABLE_TIMEDELTA
|
42
48
|
|
43
49
|
|
@@ -48,19 +54,33 @@ def check_valid_zoned_datetime(datetime: dt.datetime, /) -> None:
|
|
48
54
|
"""Check if a zoned datetime is valid."""
|
49
55
|
time_zone = ensure_time_zone(datetime) # skipif-ci-and-windows
|
50
56
|
datetime2 = datetime.replace(tzinfo=time_zone) # skipif-ci-and-windows
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
+
try: # skipif-ci-and-windows
|
58
|
+
result = (
|
59
|
+
ZonedDateTime.from_py_datetime(datetime2)
|
60
|
+
.to_tz(get_time_zone_name(UTC))
|
61
|
+
.to_tz(get_time_zone_name(time_zone))
|
62
|
+
.py_datetime()
|
63
|
+
)
|
64
|
+
except TimeZoneNotFoundError: # pragma: no cover
|
65
|
+
raise _CheckValidZonedDateTimeInvalidTimeZoneError(datetime=datetime) from None
|
57
66
|
if result != datetime2: # skipif-ci-and-windows
|
58
|
-
raise
|
67
|
+
raise _CheckValidZonedDateTimeUnequalError(datetime=datetime, result=result)
|
59
68
|
|
60
69
|
|
61
70
|
@dataclass(kw_only=True, slots=True)
|
62
|
-
class
|
71
|
+
class CheckValidZonedDateTimeError(Exception):
|
63
72
|
datetime: dt.datetime
|
73
|
+
|
74
|
+
|
75
|
+
@dataclass(kw_only=True, slots=True)
|
76
|
+
class _CheckValidZonedDateTimeInvalidTimeZoneError(CheckValidZonedDateTimeError):
|
77
|
+
@override
|
78
|
+
def __str__(self) -> str:
|
79
|
+
return f"Invalid timezone; got {self.datetime.tzinfo}" # pragma: no cover
|
80
|
+
|
81
|
+
|
82
|
+
@dataclass(kw_only=True, slots=True)
|
83
|
+
class _CheckValidZonedDateTimeUnequalError(CheckValidZonedDateTimeError):
|
64
84
|
result: dt.datetime
|
65
85
|
|
66
86
|
@override
|
@@ -138,23 +158,23 @@ class EnsureDurationError(Exception):
|
|
138
158
|
##
|
139
159
|
|
140
160
|
|
141
|
-
def
|
142
|
-
"""Ensure the object is a
|
161
|
+
def ensure_plain_datetime(datetime: DateTimeLike, /) -> dt.datetime:
|
162
|
+
"""Ensure the object is a plain datetime."""
|
143
163
|
if isinstance(datetime, dt.datetime):
|
144
164
|
return datetime
|
145
165
|
try:
|
146
|
-
return
|
147
|
-
except
|
148
|
-
raise
|
166
|
+
return parse_plain_datetime(datetime)
|
167
|
+
except ParsePlainDateTimeError as error:
|
168
|
+
raise EnsurePlainDateTimeError(datetime=error.datetime) from None
|
149
169
|
|
150
170
|
|
151
171
|
@dataclass(kw_only=True, slots=True)
|
152
|
-
class
|
172
|
+
class EnsurePlainDateTimeError(Exception):
|
153
173
|
datetime: str
|
154
174
|
|
155
175
|
@override
|
156
176
|
def __str__(self) -> str:
|
157
|
-
return f"Unable to ensure
|
177
|
+
return f"Unable to ensure plain datetime; got {self.datetime!r}"
|
158
178
|
|
159
179
|
|
160
180
|
##
|
@@ -242,7 +262,6 @@ class EnsureZonedDateTimeError(Exception):
|
|
242
262
|
##
|
243
263
|
|
244
264
|
|
245
|
-
_PARSE_DATE_YYYYMMDD_REGEX = re.compile(r"^(\d{4})(\d{2})(\d{2})$")
|
246
265
|
_PARSE_DATE_YYMMDD_REGEX = re.compile(r"^(\d{2})(\d{2})(\d{2})$")
|
247
266
|
|
248
267
|
|
@@ -250,19 +269,14 @@ def parse_date(date: str, /) -> dt.date:
|
|
250
269
|
"""Parse a string into a date."""
|
251
270
|
try:
|
252
271
|
w_date = Date.parse_common_iso(date)
|
253
|
-
except ValueError:
|
254
|
-
pass
|
255
|
-
else:
|
256
|
-
return w_date.py_date()
|
257
|
-
try:
|
258
|
-
((year, month, day),) = _PARSE_DATE_YYYYMMDD_REGEX.findall(date)
|
259
272
|
except ValueError:
|
260
273
|
try:
|
261
274
|
((year2, month, day),) = _PARSE_DATE_YYMMDD_REGEX.findall(date)
|
262
275
|
except ValueError:
|
263
276
|
raise ParseDateError(date=date) from None
|
264
277
|
year = parse_two_digit_year(year2)
|
265
|
-
|
278
|
+
return dt.date(year=int(year), month=int(month), day=int(day))
|
279
|
+
return w_date.py_date()
|
266
280
|
|
267
281
|
|
268
282
|
@dataclass(kw_only=True, slots=True)
|
@@ -279,8 +293,8 @@ class ParseDateError(Exception):
|
|
279
293
|
|
280
294
|
def parse_datetime(datetime: str, /) -> dt.datetime:
|
281
295
|
"""Parse a string into a datetime."""
|
282
|
-
with suppress(
|
283
|
-
return
|
296
|
+
with suppress(ParsePlainDateTimeError):
|
297
|
+
return parse_plain_datetime(datetime)
|
284
298
|
with suppress(ParseZonedDateTimeError):
|
285
299
|
return parse_zoned_datetime(datetime)
|
286
300
|
raise ParseDateTimeError(datetime=datetime) from None
|
@@ -320,48 +334,22 @@ class ParseDurationError(Exception):
|
|
320
334
|
##
|
321
335
|
|
322
336
|
|
323
|
-
|
324
|
-
|
325
|
-
)
|
326
|
-
|
327
|
-
|
328
|
-
def parse_local_datetime(datetime: str, /) -> dt.datetime:
|
329
|
-
"""Parse a string into a local datetime."""
|
330
|
-
try:
|
331
|
-
ldt = LocalDateTime.parse_common_iso(datetime)
|
332
|
-
except ValueError:
|
333
|
-
pass
|
334
|
-
else:
|
335
|
-
return ldt.py_datetime()
|
337
|
+
def parse_plain_datetime(datetime: str, /) -> dt.datetime:
|
338
|
+
"""Parse a string into a plain datetime."""
|
336
339
|
try:
|
337
|
-
|
338
|
-
_PARSE_LOCAL_DATETIME_REGEX.findall(datetime)
|
339
|
-
)
|
340
|
-
except ValueError:
|
341
|
-
raise ParseLocalDateTimeError(datetime=datetime) from None
|
342
|
-
try:
|
343
|
-
microsecond_use = int(microsecond)
|
340
|
+
ldt = PlainDateTime.parse_common_iso(datetime)
|
344
341
|
except ValueError:
|
345
|
-
|
346
|
-
return
|
347
|
-
year=int(year),
|
348
|
-
month=int(month),
|
349
|
-
day=int(day),
|
350
|
-
hour=int(hour),
|
351
|
-
minute=int(minute),
|
352
|
-
second=int(second),
|
353
|
-
microsecond=microsecond_use,
|
354
|
-
tzinfo=UTC,
|
355
|
-
).replace(tzinfo=None)
|
342
|
+
raise ParsePlainDateTimeError(datetime=datetime) from None
|
343
|
+
return ldt.py_datetime()
|
356
344
|
|
357
345
|
|
358
346
|
@dataclass(kw_only=True, slots=True)
|
359
|
-
class
|
347
|
+
class ParsePlainDateTimeError(Exception):
|
360
348
|
datetime: str
|
361
349
|
|
362
350
|
@override
|
363
351
|
def __str__(self) -> str:
|
364
|
-
return f"Unable to parse
|
352
|
+
return f"Unable to parse plain datetime; got {self.datetime!r}"
|
365
353
|
|
366
354
|
|
367
355
|
##
|
@@ -435,39 +423,13 @@ class _ParseTimedeltaNanosecondError(ParseTimedeltaError):
|
|
435
423
|
##
|
436
424
|
|
437
425
|
|
438
|
-
_PARSE_ZONED_DATETIME_REGEX = re.compile(
|
439
|
-
r"^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})\.?(\d{6})?\[([\w\+\-/]+)\]$"
|
440
|
-
)
|
441
|
-
|
442
|
-
|
443
426
|
def parse_zoned_datetime(datetime: str, /) -> dt.datetime:
|
444
427
|
"""Parse a string into a zoned datetime."""
|
445
428
|
try:
|
446
429
|
zdt = ZonedDateTime.parse_common_iso(datetime)
|
447
|
-
except ValueError:
|
448
|
-
pass
|
449
|
-
else:
|
450
|
-
return zdt.py_datetime()
|
451
|
-
try:
|
452
|
-
((year, month, day, hour, minute, second, microsecond, timezone),) = (
|
453
|
-
_PARSE_ZONED_DATETIME_REGEX.findall(datetime)
|
454
|
-
)
|
455
430
|
except ValueError:
|
456
431
|
raise ParseZonedDateTimeError(datetime=datetime) from None
|
457
|
-
|
458
|
-
microsecond_use = int(microsecond)
|
459
|
-
except ValueError: # skipif-ci-and-windows
|
460
|
-
microsecond_use = 0
|
461
|
-
return dt.datetime( # skipif-ci-and-windows
|
462
|
-
year=int(year),
|
463
|
-
month=int(month),
|
464
|
-
day=int(day),
|
465
|
-
hour=int(hour),
|
466
|
-
minute=int(minute),
|
467
|
-
second=int(second),
|
468
|
-
microsecond=microsecond_use,
|
469
|
-
tzinfo=ZoneInfo(timezone),
|
470
|
-
)
|
432
|
+
return zdt.py_datetime()
|
471
433
|
|
472
434
|
|
473
435
|
@dataclass(kw_only=True, slots=True)
|
@@ -494,8 +456,8 @@ def serialize_date(date: dt.date, /) -> str:
|
|
494
456
|
def serialize_datetime(datetime: dt.datetime, /) -> str:
|
495
457
|
"""Serialize a datetime."""
|
496
458
|
try:
|
497
|
-
return
|
498
|
-
except
|
459
|
+
return serialize_plain_datetime(datetime)
|
460
|
+
except SerializePlainDateTimeError:
|
499
461
|
return serialize_zoned_datetime(datetime)
|
500
462
|
|
501
463
|
|
@@ -524,22 +486,22 @@ class SerializeDurationError(Exception):
|
|
524
486
|
##
|
525
487
|
|
526
488
|
|
527
|
-
def
|
528
|
-
"""Serialize a
|
489
|
+
def serialize_plain_datetime(datetime: dt.datetime, /) -> str:
|
490
|
+
"""Serialize a plain datetime."""
|
529
491
|
try:
|
530
|
-
|
492
|
+
pdt = PlainDateTime.from_py_datetime(datetime)
|
531
493
|
except ValueError:
|
532
|
-
raise
|
533
|
-
return
|
494
|
+
raise SerializePlainDateTimeError(datetime=datetime) from None
|
495
|
+
return pdt.format_common_iso()
|
534
496
|
|
535
497
|
|
536
498
|
@dataclass(kw_only=True, slots=True)
|
537
|
-
class
|
499
|
+
class SerializePlainDateTimeError(Exception):
|
538
500
|
datetime: dt.datetime
|
539
501
|
|
540
502
|
@override
|
541
503
|
def __str__(self) -> str:
|
542
|
-
return f"Unable to serialize
|
504
|
+
return f"Unable to serialize plain datetime; got {self.datetime}"
|
543
505
|
|
544
506
|
|
545
507
|
##
|
@@ -630,43 +592,43 @@ class _ToDateTimeDeltaError(Exception):
|
|
630
592
|
__all__ = [
|
631
593
|
"MAX_SERIALIZABLE_TIMEDELTA",
|
632
594
|
"MIN_SERIALIZABLE_TIMEDELTA",
|
633
|
-
"
|
595
|
+
"CheckValidZonedDateTimeError",
|
634
596
|
"EnsureDateError",
|
635
597
|
"EnsureDateTimeError",
|
636
|
-
"
|
598
|
+
"EnsurePlainDateTimeError",
|
637
599
|
"EnsureTimeError",
|
638
600
|
"EnsureTimedeltaError",
|
639
601
|
"EnsureZonedDateTimeError",
|
640
602
|
"ParseDateError",
|
641
603
|
"ParseDateTimeError",
|
642
604
|
"ParseDurationError",
|
643
|
-
"
|
605
|
+
"ParsePlainDateTimeError",
|
644
606
|
"ParseTimeError",
|
645
607
|
"ParseTimedeltaError",
|
646
608
|
"ParseZonedDateTimeError",
|
647
609
|
"SerializeDurationError",
|
648
|
-
"
|
610
|
+
"SerializePlainDateTimeError",
|
649
611
|
"SerializeTimeDeltaError",
|
650
612
|
"SerializeZonedDateTimeError",
|
651
613
|
"check_valid_zoned_datetime",
|
652
614
|
"ensure_date",
|
653
615
|
"ensure_datetime",
|
654
616
|
"ensure_duration",
|
655
|
-
"
|
617
|
+
"ensure_plain_datetime",
|
656
618
|
"ensure_time",
|
657
619
|
"ensure_timedelta",
|
658
620
|
"ensure_zoned_datetime",
|
659
621
|
"parse_date",
|
660
622
|
"parse_datetime",
|
661
623
|
"parse_duration",
|
662
|
-
"
|
624
|
+
"parse_plain_datetime",
|
663
625
|
"parse_time",
|
664
626
|
"parse_timedelta",
|
665
627
|
"parse_zoned_datetime",
|
666
628
|
"serialize_date",
|
667
629
|
"serialize_datetime",
|
668
630
|
"serialize_duration",
|
669
|
-
"
|
631
|
+
"serialize_plain_datetime",
|
670
632
|
"serialize_time",
|
671
633
|
"serialize_timedelta",
|
672
634
|
"serialize_zoned_datetime",
|
utilities/zoneinfo.py
CHANGED
@@ -32,7 +32,7 @@ def ensure_time_zone(obj: TimeZoneLike, /) -> ZoneInfo:
|
|
32
32
|
raise _EnsureTimeZoneInvalidTZInfoError(time_zone=obj)
|
33
33
|
case dt.datetime() as datetime:
|
34
34
|
if datetime.tzinfo is None:
|
35
|
-
raise
|
35
|
+
raise _EnsureTimeZonePlainDateTimeError(datetime=datetime)
|
36
36
|
return ensure_time_zone(datetime.tzinfo)
|
37
37
|
case _ as never:
|
38
38
|
assert_never(never)
|
@@ -52,12 +52,12 @@ class _EnsureTimeZoneInvalidTZInfoError(EnsureTimeZoneError):
|
|
52
52
|
|
53
53
|
|
54
54
|
@dataclass(kw_only=True, slots=True)
|
55
|
-
class
|
55
|
+
class _EnsureTimeZonePlainDateTimeError(EnsureTimeZoneError):
|
56
56
|
datetime: dt.datetime
|
57
57
|
|
58
58
|
@override
|
59
59
|
def __str__(self) -> str:
|
60
|
-
return f"
|
60
|
+
return f"Plain datetime: {self.datetime}"
|
61
61
|
|
62
62
|
|
63
63
|
##
|
File without changes
|
File without changes
|