dycw-utilities 0.131.1__py3-none-any.whl → 0.131.2__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.1.dist-info → dycw_utilities-0.131.2.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.1.dist-info → dycw_utilities-0.131.2.dist-info}/RECORD +10 -9
- utilities/__init__.py +1 -1
- utilities/hypothesis.py +162 -3
- utilities/logging.py +1 -1
- utilities/typing.py +1 -1
- utilities/whenever.py +1 -64
- utilities/whenever2.py +139 -0
- {dycw_utilities-0.131.1.dist-info → dycw_utilities-0.131.2.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.1.dist-info → dycw_utilities-0.131.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=ajN78bRkB5Im8iLJo5SoqSMwaBs05vhc-hxvF77jai4,60
|
2
2
|
utilities/aiolimiter.py,sha256=mD0wEiqMgwpty4XTbawFpnkkmJS6R4JRsVXFUaoitSU,628
|
3
3
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
4
4
|
utilities/asyncio.py,sha256=lvdgBhuMtxq0dpiwF9g2WMMrit3kqXibN1V5NZ4xdbo,38046
|
@@ -24,7 +24,7 @@ utilities/getpass.py,sha256=DfN5UgMAtFCqS3dSfFHUfqIMZX2shXvwphOz_6J6f6A,103
|
|
24
24
|
utilities/git.py,sha256=oi7-_l5e9haSANSCvQw25ufYGoNahuUPHAZ6114s3JQ,1191
|
25
25
|
utilities/hashlib.py,sha256=SVTgtguur0P4elppvzOBbLEjVM3Pea0eWB61yg2ilxo,309
|
26
26
|
utilities/http.py,sha256=WcahTcKYRtZ04WXQoWt5EGCgFPcyHD3EJdlMfxvDt-0,946
|
27
|
-
utilities/hypothesis.py,sha256=
|
27
|
+
utilities/hypothesis.py,sha256=jiFJsS6rg4273BYDjrHT1iYH7D7ybROnH5bca9rBWqI,47372
|
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
|
@@ -32,7 +32,7 @@ utilities/iterables.py,sha256=mDqw2_0MUVp-P8FklgcaVTi2TXduH0MxbhTDzzhSBho,44915
|
|
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=0xNfcsrgFI0R9xL25LtSm-W5yhfBI93qQNT6HyaXAhg,2769
|
35
|
-
utilities/logging.py,sha256=
|
35
|
+
utilities/logging.py,sha256=DoLjy18w87fu6xDIBwiCtx3sAsNobm1QqQ4e2RRmp50,18421
|
36
36
|
utilities/luigi.py,sha256=fpH9MbxJDuo6-k9iCXRayFRtiVbUtibCJKugf7ygpv0,5988
|
37
37
|
utilities/math.py,sha256=-mQgbah-dPJwOEWf3SonrFoVZ2AVxMgpeQ3dfVa-oJA,26764
|
38
38
|
utilities/memory_profiler.py,sha256=tf2C51P2lCujPGvRt2Rfc7VEw5LDXmVPCG3z_AvBmbU,962
|
@@ -81,16 +81,17 @@ utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
|
81
81
|
utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
|
82
82
|
utilities/traceback.py,sha256=k3QhUca-643rl11S9uhibfNPlxjavOboCK56036KRcE,8859
|
83
83
|
utilities/types.py,sha256=gP04CcCOyFrG7BgblVCsrrChiuO2x842NDVW-GF7odo,18370
|
84
|
-
utilities/typing.py,sha256=
|
84
|
+
utilities/typing.py,sha256=kQWywPcRbFBKmvQBELmgbiqSHsnlo_D0ru53vl6KDeY,13846
|
85
85
|
utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
|
86
86
|
utilities/tzlocal.py,sha256=3upDNFBvGh1l9njmLR2z2S6K6VxQSb7QizYGUbAH3JU,960
|
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=jS31ZAY5OMxFxLja_Yo5Fidi87Pd-GoVZ7Vi_teqVDA,16743
|
91
|
+
utilities/whenever2.py,sha256=uub90yQg2lUC8at7lnGR30qt5iuNlqPPTePwiKckhOE,3994
|
91
92
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
92
93
|
utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
|
93
|
-
dycw_utilities-0.131.
|
94
|
-
dycw_utilities-0.131.
|
95
|
-
dycw_utilities-0.131.
|
96
|
-
dycw_utilities-0.131.
|
94
|
+
dycw_utilities-0.131.2.dist-info/METADATA,sha256=iP5y9JBuywFkkZML9iGcqagto8XgWUjD8sY4zCMWJv8,12989
|
95
|
+
dycw_utilities-0.131.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
96
|
+
dycw_utilities-0.131.2.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
97
|
+
dycw_utilities-0.131.2.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
utilities/hypothesis.py
CHANGED
@@ -47,6 +47,7 @@ from hypothesis.strategies import (
|
|
47
47
|
uuids,
|
48
48
|
)
|
49
49
|
from hypothesis.utils.conventions import not_set
|
50
|
+
from whenever import Date, DateDelta
|
50
51
|
|
51
52
|
from utilities.datetime import (
|
52
53
|
DATETIME_MAX_NAIVE,
|
@@ -88,7 +89,7 @@ from utilities.platform import IS_WINDOWS
|
|
88
89
|
from utilities.sentinel import Sentinel, sentinel
|
89
90
|
from utilities.tempfile import TEMP_DIR, TemporaryDirectory
|
90
91
|
from utilities.version import Version
|
91
|
-
from utilities.zoneinfo import UTC
|
92
|
+
from utilities.zoneinfo import UTC, ensure_time_zone
|
92
93
|
|
93
94
|
if TYPE_CHECKING:
|
94
95
|
from collections.abc import Collection, Hashable, Iterable, Iterator, Sequence
|
@@ -96,9 +97,10 @@ if TYPE_CHECKING:
|
|
96
97
|
|
97
98
|
from hypothesis.database import ExampleDatabase
|
98
99
|
from numpy.random import RandomState
|
100
|
+
from whenever import PlainDateTime, ZonedDateTime
|
99
101
|
|
100
102
|
from utilities.numpy import NDArrayB, NDArrayF, NDArrayI, NDArrayO
|
101
|
-
from utilities.types import Duration, Number, RoundMode
|
103
|
+
from utilities.types import Duration, Number, RoundMode, TimeZoneLike
|
102
104
|
|
103
105
|
|
104
106
|
_T = TypeVar("_T")
|
@@ -158,6 +160,45 @@ def bool_arrays(
|
|
158
160
|
##
|
159
161
|
|
160
162
|
|
163
|
+
@composite
|
164
|
+
def date_deltas_whenever(
|
165
|
+
draw: DrawFn,
|
166
|
+
/,
|
167
|
+
*,
|
168
|
+
min_value: MaybeSearchStrategy[DateDelta | None] = None,
|
169
|
+
max_value: MaybeSearchStrategy[DateDelta | None] = None,
|
170
|
+
) -> DateDelta:
|
171
|
+
"""Strategy for generating date deltas."""
|
172
|
+
from utilities.whenever2 import DATE_DELTA_MAX, DATE_DELTA_MIN
|
173
|
+
|
174
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
175
|
+
match min_value_:
|
176
|
+
case None:
|
177
|
+
min_value_ = DATE_DELTA_MIN
|
178
|
+
case DateDelta():
|
179
|
+
...
|
180
|
+
case _ as never:
|
181
|
+
assert_never(never)
|
182
|
+
match max_value_:
|
183
|
+
case None:
|
184
|
+
max_value_ = DATE_DELTA_MAX
|
185
|
+
case DateDelta():
|
186
|
+
...
|
187
|
+
case _ as never:
|
188
|
+
assert_never(never)
|
189
|
+
min_years, min_months, min_days = min_value_.in_years_months_days()
|
190
|
+
assert min_years == 0
|
191
|
+
assert min_months == 0
|
192
|
+
max_years, max_months, max_days = max_value_.in_years_months_days()
|
193
|
+
assert max_years == 0
|
194
|
+
assert max_months == 0
|
195
|
+
days = draw(integers(min_value=min_days, max_value=max_days))
|
196
|
+
return DateDelta(days=days)
|
197
|
+
|
198
|
+
|
199
|
+
##
|
200
|
+
|
201
|
+
|
161
202
|
@composite
|
162
203
|
def date_durations(
|
163
204
|
draw: DrawFn,
|
@@ -238,6 +279,41 @@ def dates_two_digit_year(
|
|
238
279
|
##
|
239
280
|
|
240
281
|
|
282
|
+
@composite
|
283
|
+
def dates_whenever(
|
284
|
+
draw: DrawFn,
|
285
|
+
/,
|
286
|
+
*,
|
287
|
+
min_value: MaybeSearchStrategy[Date | None] = None,
|
288
|
+
max_value: MaybeSearchStrategy[Date | None] = None,
|
289
|
+
) -> Date:
|
290
|
+
"""Strategy for generating dates."""
|
291
|
+
from utilities.whenever2 import DATE_MAX, DATE_MIN
|
292
|
+
|
293
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
294
|
+
match min_value_:
|
295
|
+
case None:
|
296
|
+
min_value_ = DATE_MIN
|
297
|
+
case Date():
|
298
|
+
...
|
299
|
+
case _ as never:
|
300
|
+
assert_never(never)
|
301
|
+
match max_value_:
|
302
|
+
case None:
|
303
|
+
max_value_ = DATE_MAX
|
304
|
+
case Date():
|
305
|
+
...
|
306
|
+
case _ as never:
|
307
|
+
assert_never(never)
|
308
|
+
py_date = draw(
|
309
|
+
dates(min_value=min_value_.py_date(), max_value=max_value_.py_date())
|
310
|
+
)
|
311
|
+
return Date.from_py_date(py_date)
|
312
|
+
|
313
|
+
|
314
|
+
##
|
315
|
+
|
316
|
+
|
241
317
|
@composite
|
242
318
|
def datetime_durations(
|
243
319
|
draw: DrawFn,
|
@@ -920,7 +996,7 @@ def _pairs_map(elements: list[_T], /) -> tuple[_T, _T]:
|
|
920
996
|
|
921
997
|
def paths() -> SearchStrategy[Path]:
|
922
998
|
"""Strategy for generating `Path`s."""
|
923
|
-
reserved = {"NUL"}
|
999
|
+
reserved = {"AUX", "NUL"}
|
924
1000
|
strategy = text_ascii(min_size=1, max_size=10).filter(lambda x: x not in reserved)
|
925
1001
|
return lists(strategy, max_size=10).map(lambda parts: Path(*parts))
|
926
1002
|
|
@@ -965,6 +1041,45 @@ class PlainDateTimesError(Exception):
|
|
965
1041
|
##
|
966
1042
|
|
967
1043
|
|
1044
|
+
@composite
|
1045
|
+
def plain_datetimes_whenever(
|
1046
|
+
draw: DrawFn,
|
1047
|
+
/,
|
1048
|
+
*,
|
1049
|
+
min_value: MaybeSearchStrategy[PlainDateTime | None] = None,
|
1050
|
+
max_value: MaybeSearchStrategy[PlainDateTime | None] = None,
|
1051
|
+
) -> PlainDateTime:
|
1052
|
+
"""Strategy for generating plain datetimes."""
|
1053
|
+
from whenever import PlainDateTime
|
1054
|
+
|
1055
|
+
from utilities.whenever2 import PLAIN_DATE_TIME_MAX, PLAIN_DATE_TIME_MIN
|
1056
|
+
|
1057
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
1058
|
+
match min_value_:
|
1059
|
+
case None:
|
1060
|
+
min_value_ = PLAIN_DATE_TIME_MIN
|
1061
|
+
case PlainDateTime():
|
1062
|
+
...
|
1063
|
+
case _ as never:
|
1064
|
+
assert_never(never)
|
1065
|
+
match max_value_:
|
1066
|
+
case None:
|
1067
|
+
max_value_ = PLAIN_DATE_TIME_MAX
|
1068
|
+
case PlainDateTime():
|
1069
|
+
...
|
1070
|
+
case _ as never:
|
1071
|
+
assert_never(never)
|
1072
|
+
py_datetime = draw(
|
1073
|
+
datetimes(
|
1074
|
+
min_value=min_value_.py_datetime(), max_value=max_value_.py_datetime()
|
1075
|
+
)
|
1076
|
+
)
|
1077
|
+
return PlainDateTime.from_py_datetime(py_datetime)
|
1078
|
+
|
1079
|
+
|
1080
|
+
##
|
1081
|
+
|
1082
|
+
|
968
1083
|
@composite
|
969
1084
|
def random_states(
|
970
1085
|
draw: DrawFn, /, *, seed: MaybeSearchStrategy[int | None] = None
|
@@ -1427,6 +1542,46 @@ class ZonedDateTimesError(Exception):
|
|
1427
1542
|
return "Rounding requires a timedelta; got None"
|
1428
1543
|
|
1429
1544
|
|
1545
|
+
##
|
1546
|
+
|
1547
|
+
|
1548
|
+
@composite
|
1549
|
+
def zoned_datetimes_whenever(
|
1550
|
+
draw: DrawFn,
|
1551
|
+
/,
|
1552
|
+
*,
|
1553
|
+
min_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
|
1554
|
+
max_value: MaybeSearchStrategy[PlainDateTime | ZonedDateTime | None] = None,
|
1555
|
+
time_zone: MaybeSearchStrategy[TimeZoneLike] = UTC,
|
1556
|
+
) -> ZonedDateTime:
|
1557
|
+
"""Strategy for generating zoned datetimes."""
|
1558
|
+
from whenever import PlainDateTime, ZonedDateTime
|
1559
|
+
|
1560
|
+
min_value_, max_value_ = [draw2(draw, v) for v in [min_value, max_value]]
|
1561
|
+
time_zone_ = ensure_time_zone(draw2(draw, time_zone))
|
1562
|
+
match min_value_:
|
1563
|
+
case None | PlainDateTime():
|
1564
|
+
...
|
1565
|
+
case ZonedDateTime():
|
1566
|
+
with assume_does_not_raise(ValueError):
|
1567
|
+
min_value_ = min_value_.to_tz(time_zone_.key).to_plain()
|
1568
|
+
case _ as never:
|
1569
|
+
assert_never(never)
|
1570
|
+
match max_value_:
|
1571
|
+
case None | PlainDateTime():
|
1572
|
+
...
|
1573
|
+
case ZonedDateTime():
|
1574
|
+
with assume_does_not_raise(ValueError):
|
1575
|
+
max_value_ = max_value_.to_tz(time_zone_.key).to_plain()
|
1576
|
+
case _ as never:
|
1577
|
+
assert_never(never)
|
1578
|
+
plain_datetime = draw(
|
1579
|
+
plain_datetimes_whenever(min_value=min_value_, max_value=max_value_)
|
1580
|
+
)
|
1581
|
+
with assume_does_not_raise(ValueError):
|
1582
|
+
return plain_datetime.assume_tz(time_zone_.key, disambiguate="raise")
|
1583
|
+
|
1584
|
+
|
1430
1585
|
__all__ = [
|
1431
1586
|
"Draw2Error",
|
1432
1587
|
"MaybeSearchStrategy",
|
@@ -1435,8 +1590,10 @@ __all__ = [
|
|
1435
1590
|
"ZonedDateTimesError",
|
1436
1591
|
"assume_does_not_raise",
|
1437
1592
|
"bool_arrays",
|
1593
|
+
"date_deltas_whenever",
|
1438
1594
|
"date_durations",
|
1439
1595
|
"dates_two_digit_year",
|
1596
|
+
"dates_whenever",
|
1440
1597
|
"datetime_durations",
|
1441
1598
|
"draw2",
|
1442
1599
|
"float32s",
|
@@ -1460,6 +1617,7 @@ __all__ = [
|
|
1460
1617
|
"paths",
|
1461
1618
|
"plain_datetimes",
|
1462
1619
|
"plain_datetimes",
|
1620
|
+
"plain_datetimes_whenever",
|
1463
1621
|
"random_states",
|
1464
1622
|
"sentinels",
|
1465
1623
|
"sets_fixed_length",
|
@@ -1480,4 +1638,5 @@ __all__ = [
|
|
1480
1638
|
"uint64s",
|
1481
1639
|
"versions",
|
1482
1640
|
"zoned_datetimes",
|
1641
|
+
"zoned_datetimes_whenever",
|
1483
1642
|
]
|
utilities/logging.py
CHANGED
@@ -190,7 +190,7 @@ def get_formatter(
|
|
190
190
|
) -> Formatter:
|
191
191
|
"""Get the formatter; colored if available."""
|
192
192
|
if whenever:
|
193
|
-
from utilities.
|
193
|
+
from utilities.whenever2 import WheneverLogRecord
|
194
194
|
|
195
195
|
setLogRecordFactory(WheneverLogRecord)
|
196
196
|
format_ = format_.replace("{asctime}", "{zoned_datetime}")
|
utilities/typing.py
CHANGED
@@ -234,7 +234,7 @@ def is_instance_gen(obj: Any, type_: Any, /) -> bool:
|
|
234
234
|
"""Check if an instance relationship holds, except bool<int."""
|
235
235
|
# parent
|
236
236
|
if isinstance(type_, tuple):
|
237
|
-
return any(is_instance_gen(obj, t) for t in type_)
|
237
|
+
return any(is_instance_gen(obj, t) for t in type_) # skipif-ci-and-not-windows
|
238
238
|
if is_literal_type(type_):
|
239
239
|
return obj in get_args(type_)
|
240
240
|
if is_union_type(type_):
|
utilities/whenever.py
CHANGED
@@ -4,9 +4,7 @@ import datetime as dt
|
|
4
4
|
import re
|
5
5
|
from contextlib import suppress
|
6
6
|
from dataclasses import dataclass
|
7
|
-
from
|
8
|
-
from logging import LogRecord
|
9
|
-
from typing import TYPE_CHECKING, Any, override
|
7
|
+
from typing import TYPE_CHECKING, override
|
10
8
|
|
11
9
|
from whenever import (
|
12
10
|
Date,
|
@@ -35,8 +33,6 @@ from utilities.re import (
|
|
35
33
|
from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
|
36
34
|
|
37
35
|
if TYPE_CHECKING:
|
38
|
-
from zoneinfo import ZoneInfo
|
39
|
-
|
40
36
|
from utilities.types import (
|
41
37
|
DateLike,
|
42
38
|
DateTimeLike,
|
@@ -565,64 +561,6 @@ class SerializeZonedDateTimeError(Exception):
|
|
565
561
|
##
|
566
562
|
|
567
563
|
|
568
|
-
class WheneverLogRecord(LogRecord):
|
569
|
-
"""Log record powered by `whenever`."""
|
570
|
-
|
571
|
-
zoned_datetime: str
|
572
|
-
|
573
|
-
@override
|
574
|
-
def __init__(
|
575
|
-
self,
|
576
|
-
name: str,
|
577
|
-
level: int,
|
578
|
-
pathname: str,
|
579
|
-
lineno: int,
|
580
|
-
msg: object,
|
581
|
-
args: Any,
|
582
|
-
exc_info: Any,
|
583
|
-
func: str | None = None,
|
584
|
-
sinfo: str | None = None,
|
585
|
-
) -> None:
|
586
|
-
super().__init__(
|
587
|
-
name, level, pathname, lineno, msg, args, exc_info, func, sinfo
|
588
|
-
)
|
589
|
-
length = self._get_length()
|
590
|
-
plain = format(self._get_now().to_plain().format_common_iso(), f"{length}s")
|
591
|
-
time_zone = self._get_time_zone_key()
|
592
|
-
self.zoned_datetime = f"{plain}[{time_zone}]"
|
593
|
-
|
594
|
-
@classmethod
|
595
|
-
@cache
|
596
|
-
def _get_time_zone(cls) -> ZoneInfo:
|
597
|
-
"""Get the local timezone."""
|
598
|
-
try:
|
599
|
-
from utilities.tzlocal import get_local_time_zone
|
600
|
-
except ModuleNotFoundError: # pragma: no cover
|
601
|
-
return UTC
|
602
|
-
return get_local_time_zone()
|
603
|
-
|
604
|
-
@classmethod
|
605
|
-
@cache
|
606
|
-
def _get_time_zone_key(cls) -> str:
|
607
|
-
"""Get the local timezone as a string."""
|
608
|
-
return cls._get_time_zone().key
|
609
|
-
|
610
|
-
@classmethod
|
611
|
-
@cache
|
612
|
-
def _get_length(cls) -> int:
|
613
|
-
"""Get maximum length of a formatted string."""
|
614
|
-
now = cls._get_now().replace(nanosecond=1000).to_plain()
|
615
|
-
return len(now.format_common_iso())
|
616
|
-
|
617
|
-
@classmethod
|
618
|
-
def _get_now(cls) -> ZonedDateTime:
|
619
|
-
"""Get the current zoned datetime."""
|
620
|
-
return ZonedDateTime.now(cls._get_time_zone().key)
|
621
|
-
|
622
|
-
|
623
|
-
##
|
624
|
-
|
625
|
-
|
626
564
|
def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
|
627
565
|
"""Serialize a timedelta."""
|
628
566
|
total_microseconds = datetime_duration_to_microseconds(timedelta)
|
@@ -672,7 +610,6 @@ __all__ = [
|
|
672
610
|
"SerializePlainDateTimeError",
|
673
611
|
"SerializeTimeDeltaError",
|
674
612
|
"SerializeZonedDateTimeError",
|
675
|
-
"WheneverLogRecord",
|
676
613
|
"check_valid_zoned_datetime",
|
677
614
|
"ensure_date",
|
678
615
|
"ensure_datetime",
|
utilities/whenever2.py
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import datetime as dt
|
4
|
+
from functools import cache
|
5
|
+
from logging import LogRecord
|
6
|
+
from typing import TYPE_CHECKING, Any, override
|
7
|
+
|
8
|
+
from whenever import Date, DateTimeDelta, PlainDateTime, ZonedDateTime
|
9
|
+
|
10
|
+
from utilities.zoneinfo import UTC, get_time_zone_name
|
11
|
+
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from zoneinfo import ZoneInfo
|
14
|
+
|
15
|
+
from utilities.types import TimeZoneLike
|
16
|
+
|
17
|
+
|
18
|
+
DATE_MIN = Date.from_py_date(dt.date.min)
|
19
|
+
DATE_MAX = Date.from_py_date(dt.date.max)
|
20
|
+
PLAIN_DATE_TIME_MIN = PlainDateTime.from_py_datetime(dt.datetime.min) # noqa: DTZ901
|
21
|
+
PLAIN_DATE_TIME_MAX = PlainDateTime.from_py_datetime(dt.datetime.max) # noqa: DTZ901
|
22
|
+
ZONED_DATE_TIME_MIN = PLAIN_DATE_TIME_MIN.assume_tz(UTC.key)
|
23
|
+
ZONED_DATE_TIME_MAX = PLAIN_DATE_TIME_MAX.assume_tz(UTC.key)
|
24
|
+
DATE_TIME_DELTA_MIN = DateTimeDelta(days=-3652059, seconds=-316192377600)
|
25
|
+
DATE_TIME_DELTA_MAX = DateTimeDelta(days=3652059, seconds=316192377600)
|
26
|
+
DATE_DELTA_MIN = DATE_TIME_DELTA_MIN.date_part()
|
27
|
+
DATE_DELTA_MAX = DATE_TIME_DELTA_MAX.date_part()
|
28
|
+
TIME_DELTA_MIN = DATE_TIME_DELTA_MIN.time_part()
|
29
|
+
TIME_DELTA_MAX = DATE_TIME_DELTA_MAX.time_part()
|
30
|
+
|
31
|
+
|
32
|
+
##
|
33
|
+
|
34
|
+
|
35
|
+
def from_timestamp(i: float, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
|
36
|
+
"""Get a zoned datetime from a timestamp."""
|
37
|
+
return ZonedDateTime.from_timestamp(i, tz=get_time_zone_name(time_zone))
|
38
|
+
|
39
|
+
|
40
|
+
def from_timestamp_millis(i: int, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
|
41
|
+
"""Get a zoned datetime from a timestamp (in milliseconds)."""
|
42
|
+
return ZonedDateTime.from_timestamp_millis(i, tz=get_time_zone_name(time_zone))
|
43
|
+
|
44
|
+
|
45
|
+
def from_timestamp_nanos(i: int, /, *, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
|
46
|
+
"""Get a zoned datetime from a timestamp (in nanoseconds)."""
|
47
|
+
return ZonedDateTime.from_timestamp_nanos(i, tz=get_time_zone_name(time_zone))
|
48
|
+
|
49
|
+
|
50
|
+
##
|
51
|
+
|
52
|
+
|
53
|
+
def get_now(*, time_zone: TimeZoneLike = UTC) -> ZonedDateTime:
|
54
|
+
"""Get the current zoned datetime."""
|
55
|
+
return ZonedDateTime.now(get_time_zone_name(time_zone))
|
56
|
+
|
57
|
+
|
58
|
+
NOW_UTC = get_now(time_zone=UTC)
|
59
|
+
|
60
|
+
|
61
|
+
def get_now_local() -> ZonedDateTime:
|
62
|
+
"""Get the current local time."""
|
63
|
+
return get_now(time_zone="local")
|
64
|
+
|
65
|
+
|
66
|
+
##
|
67
|
+
|
68
|
+
|
69
|
+
class WheneverLogRecord(LogRecord):
|
70
|
+
"""Log record powered by `whenever`."""
|
71
|
+
|
72
|
+
zoned_datetime: str
|
73
|
+
|
74
|
+
@override
|
75
|
+
def __init__(
|
76
|
+
self,
|
77
|
+
name: str,
|
78
|
+
level: int,
|
79
|
+
pathname: str,
|
80
|
+
lineno: int,
|
81
|
+
msg: object,
|
82
|
+
args: Any,
|
83
|
+
exc_info: Any,
|
84
|
+
func: str | None = None,
|
85
|
+
sinfo: str | None = None,
|
86
|
+
) -> None:
|
87
|
+
super().__init__(
|
88
|
+
name, level, pathname, lineno, msg, args, exc_info, func, sinfo
|
89
|
+
)
|
90
|
+
length = self._get_length()
|
91
|
+
plain = format(get_now_local().to_plain().format_common_iso(), f"{length}s")
|
92
|
+
time_zone = self._get_time_zone_key()
|
93
|
+
self.zoned_datetime = f"{plain}[{time_zone}]"
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
@cache
|
97
|
+
def _get_time_zone(cls) -> ZoneInfo:
|
98
|
+
"""Get the local timezone."""
|
99
|
+
try:
|
100
|
+
from utilities.tzlocal import get_local_time_zone
|
101
|
+
except ModuleNotFoundError: # pragma: no cover
|
102
|
+
return UTC
|
103
|
+
return get_local_time_zone()
|
104
|
+
|
105
|
+
@classmethod
|
106
|
+
@cache
|
107
|
+
def _get_time_zone_key(cls) -> str:
|
108
|
+
"""Get the local timezone as a string."""
|
109
|
+
return cls._get_time_zone().key
|
110
|
+
|
111
|
+
@classmethod
|
112
|
+
@cache
|
113
|
+
def _get_length(cls) -> int:
|
114
|
+
"""Get maximum length of a formatted string."""
|
115
|
+
now = get_now_local().replace(nanosecond=1000).to_plain()
|
116
|
+
return len(now.format_common_iso())
|
117
|
+
|
118
|
+
|
119
|
+
__all__ = [
|
120
|
+
"DATE_DELTA_MAX",
|
121
|
+
"DATE_DELTA_MIN",
|
122
|
+
"DATE_MAX",
|
123
|
+
"DATE_MIN",
|
124
|
+
"DATE_TIME_DELTA_MAX",
|
125
|
+
"DATE_TIME_DELTA_MIN",
|
126
|
+
"PLAIN_DATE_TIME_MAX",
|
127
|
+
"PLAIN_DATE_TIME_MIN",
|
128
|
+
"TIME_DELTA_MAX",
|
129
|
+
"TIME_DELTA_MIN",
|
130
|
+
"ZONED_DATE_TIME_MAX",
|
131
|
+
"ZONED_DATE_TIME_MIN",
|
132
|
+
"WheneverLogRecord",
|
133
|
+
"from_timestamp",
|
134
|
+
"from_timestamp_millis",
|
135
|
+
"from_timestamp_nanos",
|
136
|
+
"get_now",
|
137
|
+
"get_now",
|
138
|
+
"get_now_local",
|
139
|
+
]
|
File without changes
|
File without changes
|