dycw-utilities 0.131.10__py3-none-any.whl → 0.131.12__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.10.dist-info → dycw_utilities-0.131.12.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.10.dist-info → dycw_utilities-0.131.12.dist-info}/RECORD +21 -21
- utilities/__init__.py +1 -1
- utilities/click.py +6 -6
- utilities/datetime.py +11 -18
- utilities/fastapi.py +2 -4
- utilities/fpdf2.py +2 -2
- utilities/hypothesis.py +8 -11
- utilities/logging.py +54 -69
- utilities/math.py +10 -9
- utilities/orjson.py +46 -45
- utilities/pyinstrument.py +2 -3
- utilities/re.py +12 -13
- utilities/traceback.py +18 -21
- utilities/types.py +49 -10
- utilities/typing.py +25 -1
- utilities/tzlocal.py +2 -26
- utilities/whenever.py +6 -6
- utilities/whenever2.py +68 -2
- {dycw_utilities-0.131.10.dist-info → dycw_utilities-0.131.12.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.10.dist-info → dycw_utilities-0.131.12.dist-info}/licenses/LICENSE +0 -0
utilities/math.py
CHANGED
@@ -8,9 +8,10 @@ from re import Match, search
|
|
8
8
|
from typing import TYPE_CHECKING, Literal, assert_never, overload, override
|
9
9
|
|
10
10
|
from utilities.errors import ImpossibleCaseError
|
11
|
+
from utilities.re import ExtractGroupsError, extract_groups
|
11
12
|
|
12
13
|
if TYPE_CHECKING:
|
13
|
-
from utilities.types import
|
14
|
+
from utilities.types import MathRoundMode, Number, Sign
|
14
15
|
|
15
16
|
|
16
17
|
MIN_FLOAT32, MAX_FLOAT32 = -3.4028234663852886e38, 3.4028234663852886e38
|
@@ -708,7 +709,7 @@ def round_(
|
|
708
709
|
x: float,
|
709
710
|
/,
|
710
711
|
*,
|
711
|
-
mode:
|
712
|
+
mode: MathRoundMode = "standard",
|
712
713
|
rel_tol: float | None = None,
|
713
714
|
abs_tol: float | None = None,
|
714
715
|
) -> int:
|
@@ -748,7 +749,7 @@ def round_(
|
|
748
749
|
|
749
750
|
def _round_tie_standard(
|
750
751
|
x: float,
|
751
|
-
mode:
|
752
|
+
mode: MathRoundMode,
|
752
753
|
/,
|
753
754
|
*,
|
754
755
|
rel_tol: float | None = None,
|
@@ -757,9 +758,9 @@ def _round_tie_standard(
|
|
757
758
|
"""Round a float to an integer using the standard method."""
|
758
759
|
frac, _ = modf(x)
|
759
760
|
if _is_close(abs(frac), 0.5, rel_tol=rel_tol, abs_tol=abs_tol):
|
760
|
-
mode_use:
|
761
|
+
mode_use: MathRoundMode = mode
|
761
762
|
else:
|
762
|
-
mode_use:
|
763
|
+
mode_use: MathRoundMode = "standard"
|
763
764
|
return round_(x, mode=mode_use)
|
764
765
|
|
765
766
|
|
@@ -775,9 +776,9 @@ def round_float_imprecisions(
|
|
775
776
|
) -> float:
|
776
777
|
"""Round a float, removing binary representation imprecisions."""
|
777
778
|
try:
|
778
|
-
|
779
|
-
except
|
780
|
-
|
779
|
+
head, tail = extract_groups(_ROUND_FLOAT_IMPRECISIONS_PATTERN, str(x))
|
780
|
+
except ExtractGroupsError:
|
781
|
+
head, tail = extract_groups(_ROUND_FLOAT_IMPRECISIONS_PATTERN, f"{x:.20f}")
|
781
782
|
half = ceil(decimals / 2)
|
782
783
|
pattern0 = search(rf"^([0-9]+?)(0{{{half},}})([0-9]+?)$", tail)
|
783
784
|
pattern9 = search(rf"^(0*)([0-9]+?)(9{{{half},}})([0-9]+?)$", tail)
|
@@ -823,7 +824,7 @@ def round_to_float(
|
|
823
824
|
y: float,
|
824
825
|
/,
|
825
826
|
*,
|
826
|
-
mode:
|
827
|
+
mode: MathRoundMode = "standard",
|
827
828
|
rel_tol: float | None = None,
|
828
829
|
abs_tol: float | None = None,
|
829
830
|
) -> float:
|
utilities/orjson.py
CHANGED
@@ -14,6 +14,7 @@ from pathlib import Path
|
|
14
14
|
from re import Pattern, search
|
15
15
|
from typing import TYPE_CHECKING, Any, Literal, Self, assert_never, overload, override
|
16
16
|
from uuid import UUID
|
17
|
+
from zoneinfo import ZoneInfo
|
17
18
|
|
18
19
|
from orjson import (
|
19
20
|
OPT_PASSTHROUGH_DATACLASS,
|
@@ -22,6 +23,7 @@ from orjson import (
|
|
22
23
|
dumps,
|
23
24
|
loads,
|
24
25
|
)
|
26
|
+
from whenever import ZonedDateTime
|
25
27
|
|
26
28
|
from utilities.concurrent import concurrent_map
|
27
29
|
from utilities.dataclasses import dataclass_to_dict
|
@@ -35,15 +37,8 @@ from utilities.iterables import (
|
|
35
37
|
)
|
36
38
|
from utilities.logging import get_logging_level_number
|
37
39
|
from utilities.math import MAX_INT64, MIN_INT64
|
38
|
-
from utilities.types import
|
39
|
-
|
40
|
-
DateOrDateTime,
|
41
|
-
LogLevel,
|
42
|
-
MaybeIterable,
|
43
|
-
PathLike,
|
44
|
-
StrMapping,
|
45
|
-
)
|
46
|
-
from utilities.tzlocal import get_local_time_zone
|
40
|
+
from utilities.types import Dataclass, LogLevel, MaybeIterable, PathLike, StrMapping
|
41
|
+
from utilities.tzlocal import LOCAL_TIME_ZONE
|
47
42
|
from utilities.uuid import UUID_PATTERN
|
48
43
|
from utilities.version import Version, parse_version
|
49
44
|
from utilities.whenever import (
|
@@ -57,12 +52,14 @@ from utilities.whenever import (
|
|
57
52
|
serialize_time,
|
58
53
|
serialize_timedelta,
|
59
54
|
)
|
60
|
-
from utilities.
|
55
|
+
from utilities.whenever2 import from_timestamp
|
61
56
|
|
62
57
|
if TYPE_CHECKING:
|
63
58
|
from collections.abc import Set as AbstractSet
|
64
59
|
from logging import _FormatStyle
|
65
60
|
|
61
|
+
from whenever import Date
|
62
|
+
|
66
63
|
from utilities.types import Parallelism
|
67
64
|
|
68
65
|
|
@@ -92,6 +89,7 @@ class _Prefixes(Enum):
|
|
92
89
|
unserializable = "un"
|
93
90
|
uuid = "uu"
|
94
91
|
version = "v"
|
92
|
+
zoned_datetime = "zd"
|
95
93
|
|
96
94
|
|
97
95
|
type _DataclassHook = Callable[[type[Dataclass], StrMapping], StrMapping]
|
@@ -194,6 +192,8 @@ def _pre_process(
|
|
194
192
|
return f"[{_Prefixes.exception_class.value}|{error_cls.__qualname__}]"
|
195
193
|
case Version() as version:
|
196
194
|
return f"[{_Prefixes.version.value}]{version!s}"
|
195
|
+
case ZonedDateTime() as datetime:
|
196
|
+
return f"[{_Prefixes.zoned_datetime.value}]{datetime}"
|
197
197
|
# contains
|
198
198
|
case Dataclass() as dataclass:
|
199
199
|
asdict = dataclass_to_dict(
|
@@ -350,6 +350,11 @@ _ZONED_DATETIME_PATTERN = re.compile(
|
|
350
350
|
+ _Prefixes.datetime.value
|
351
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
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
|
+
)
|
353
358
|
|
354
359
|
|
355
360
|
def _make_unit_pattern(prefix: _Prefixes, /) -> Pattern[str]:
|
@@ -437,6 +442,8 @@ def _object_hook(
|
|
437
442
|
return parse_version(match.group(1))
|
438
443
|
if match := _ZONED_DATETIME_PATTERN.search(text):
|
439
444
|
return parse_zoned_datetime(match.group(1))
|
445
|
+
if match := _ZONED_DATETIME_PATTERN2.search(text):
|
446
|
+
return ZonedDateTime.parse_common_iso(match.group(1))
|
440
447
|
if (
|
441
448
|
exc_class := _object_hook_exception_class(
|
442
449
|
text, data=data, objects=objects, redirects=redirects
|
@@ -753,9 +760,7 @@ class OrjsonFormatter(Formatter):
|
|
753
760
|
path_name=Path(record.pathname),
|
754
761
|
line_num=record.lineno,
|
755
762
|
message=record.getMessage(),
|
756
|
-
datetime=
|
757
|
-
record.created, tz=get_local_time_zone()
|
758
|
-
),
|
763
|
+
datetime=from_timestamp(record.created, time_zone="local"),
|
759
764
|
func_name=record.funcName,
|
760
765
|
extra=extra if len(extra) >= 1 else None,
|
761
766
|
)
|
@@ -859,11 +864,16 @@ class GetLogRecordsOutput:
|
|
859
864
|
for r in self.records
|
860
865
|
]
|
861
866
|
if len(records) >= 1:
|
862
|
-
time_zone = one_unique(
|
867
|
+
time_zone = one_unique(ZoneInfo(r.datetime.tz) for r in records)
|
863
868
|
else:
|
864
|
-
time_zone =
|
869
|
+
time_zone = LOCAL_TIME_ZONE
|
865
870
|
return DataFrame(
|
866
|
-
data=[
|
871
|
+
data=[
|
872
|
+
dataclass_to_dict(
|
873
|
+
replace(r, datetime=r.datetime.py_datetime()), recursive=False
|
874
|
+
)
|
875
|
+
for r in records
|
876
|
+
],
|
867
877
|
schema={
|
868
878
|
"index": UInt64,
|
869
879
|
"name": String,
|
@@ -891,9 +901,12 @@ class GetLogRecordsOutput:
|
|
891
901
|
level: LogLevel | None = None,
|
892
902
|
min_level: LogLevel | None = None,
|
893
903
|
max_level: LogLevel | None = None,
|
894
|
-
|
895
|
-
|
896
|
-
|
904
|
+
date: Date | None = None,
|
905
|
+
min_date: Date | None = None,
|
906
|
+
max_date: Date | None = None,
|
907
|
+
datetime: ZonedDateTime | None = None,
|
908
|
+
min_datetime: ZonedDateTime | None = None,
|
909
|
+
max_datetime: ZonedDateTime | None = None,
|
897
910
|
func_name: bool | str | None = None,
|
898
911
|
extra: bool | MaybeIterable[str] | None = None,
|
899
912
|
log_file: bool | PathLike | None = None,
|
@@ -932,30 +945,18 @@ class GetLogRecordsOutput:
|
|
932
945
|
records = [
|
933
946
|
r for r in records if r.level <= get_logging_level_number(max_level)
|
934
947
|
]
|
935
|
-
if
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
if
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
case dt.date() as min_date:
|
948
|
-
records = [r for r in records if r.date >= min_date]
|
949
|
-
case _ as never:
|
950
|
-
assert_never(never)
|
951
|
-
if max_date_or_datetime is not None:
|
952
|
-
match max_date_or_datetime:
|
953
|
-
case dt.datetime() as max_datetime:
|
954
|
-
records = [r for r in records if r.datetime <= max_datetime]
|
955
|
-
case dt.date() as max_date:
|
956
|
-
records = [r for r in records if r.date <= max_date]
|
957
|
-
case _ as never:
|
958
|
-
assert_never(never)
|
948
|
+
if date is not None:
|
949
|
+
records = [r for r in records if r.date == date]
|
950
|
+
if min_date is not None:
|
951
|
+
records = [r for r in records if r.date >= min_date]
|
952
|
+
if max_date is not None:
|
953
|
+
records = [r for r in records if r.date <= max_date]
|
954
|
+
if datetime is not None:
|
955
|
+
records = [r for r in records if r.datetime == datetime]
|
956
|
+
if min_datetime is not None:
|
957
|
+
records = [r for r in records if r.datetime >= min_datetime]
|
958
|
+
if max_datetime is not None:
|
959
|
+
records = [r for r in records if r.datetime <= max_datetime]
|
959
960
|
if func_name is not None:
|
960
961
|
match func_name:
|
961
962
|
case bool() as has_func_name:
|
@@ -1058,7 +1059,7 @@ class OrjsonLogRecord:
|
|
1058
1059
|
level: int
|
1059
1060
|
path_name: Path
|
1060
1061
|
line_num: int
|
1061
|
-
datetime:
|
1062
|
+
datetime: ZonedDateTime
|
1062
1063
|
func_name: str | None = None
|
1063
1064
|
stack_info: str | None = None
|
1064
1065
|
extra: StrMapping | None = None
|
@@ -1066,7 +1067,7 @@ class OrjsonLogRecord:
|
|
1066
1067
|
log_file_line_num: int | None = None
|
1067
1068
|
|
1068
1069
|
@cached_property
|
1069
|
-
def date(self) ->
|
1070
|
+
def date(self) -> Date:
|
1070
1071
|
return self.datetime.date()
|
1071
1072
|
|
1072
1073
|
|
utilities/pyinstrument.py
CHANGED
@@ -7,9 +7,8 @@ from typing import TYPE_CHECKING
|
|
7
7
|
from pyinstrument.profiler import Profiler
|
8
8
|
|
9
9
|
from utilities.atomicwrites import writer
|
10
|
-
from utilities.datetime import serialize_compact
|
11
10
|
from utilities.pathlib import get_path
|
12
|
-
from utilities.
|
11
|
+
from utilities.whenever2 import format_compact, get_now
|
13
12
|
|
14
13
|
if TYPE_CHECKING:
|
15
14
|
from collections.abc import Iterator
|
@@ -23,7 +22,7 @@ def profile(*, path: MaybeCallablePathLike | None = Path.cwd) -> Iterator[None]:
|
|
23
22
|
with Profiler() as profiler:
|
24
23
|
yield
|
25
24
|
filename = get_path(path=path).joinpath(
|
26
|
-
f"profile__{
|
25
|
+
f"profile__{format_compact(get_now())}.html"
|
27
26
|
)
|
28
27
|
with writer(filename) as temp, temp.open(mode="w") as fh:
|
29
28
|
_ = fh.write(profiler.output_html())
|
utilities/re.py
CHANGED
@@ -5,8 +5,6 @@ from dataclasses import dataclass
|
|
5
5
|
from re import Pattern
|
6
6
|
from typing import TYPE_CHECKING, assert_never, override
|
7
7
|
|
8
|
-
from utilities.iterables import OneEmptyError, OneNonUniqueError, one
|
9
|
-
|
10
8
|
if TYPE_CHECKING:
|
11
9
|
from utilities.types import PatternLike
|
12
10
|
|
@@ -36,16 +34,17 @@ def extract_group(pattern: PatternLike, text: str, /, *, flags: int = 0) -> str:
|
|
36
34
|
raise _ExtractGroupNoCaptureGroupsError(pattern=pattern_use, text=text)
|
37
35
|
case 1:
|
38
36
|
matches: list[str] = pattern_use.findall(text)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
37
|
+
match len(matches):
|
38
|
+
case 0:
|
39
|
+
raise _ExtractGroupNoMatchesError(
|
40
|
+
pattern=pattern_use, text=text
|
41
|
+
) from None
|
42
|
+
case 1:
|
43
|
+
return matches[0]
|
44
|
+
case _:
|
45
|
+
raise _ExtractGroupMultipleMatchesError(
|
46
|
+
pattern=pattern_use, text=text, matches=matches
|
47
|
+
) from None
|
49
48
|
case _:
|
50
49
|
raise _ExtractGroupMultipleCaptureGroupsError(
|
51
50
|
pattern=pattern_use, text=text
|
@@ -109,7 +108,7 @@ def extract_groups(pattern: PatternLike, text: str, /, *, flags: int = 0) -> lis
|
|
109
108
|
case 1, 1:
|
110
109
|
return matches
|
111
110
|
case 1, _:
|
112
|
-
return list(
|
111
|
+
return list(matches[0])
|
113
112
|
case _:
|
114
113
|
raise _ExtractGroupsMultipleMatchesError(
|
115
114
|
pattern=pattern_use, text=text, matches=matches
|
utilities/traceback.py
CHANGED
@@ -14,7 +14,6 @@ from traceback import TracebackException
|
|
14
14
|
from typing import TYPE_CHECKING, override
|
15
15
|
|
16
16
|
from utilities.atomicwrites import writer
|
17
|
-
from utilities.datetime import get_datetime, get_now, serialize_compact
|
18
17
|
from utilities.errors import repr_error
|
19
18
|
from utilities.iterables import OneEmptyError, one
|
20
19
|
from utilities.pathlib import get_path
|
@@ -27,16 +26,19 @@ from utilities.reprlib import (
|
|
27
26
|
RICH_MAX_WIDTH,
|
28
27
|
yield_mapping_repr,
|
29
28
|
)
|
30
|
-
from utilities.tzlocal import get_local_time_zone, get_now_local
|
31
29
|
from utilities.version import get_version
|
32
|
-
from utilities.
|
30
|
+
from utilities.whenever2 import format_compact, get_now, to_zoned_date_time
|
33
31
|
|
34
32
|
if TYPE_CHECKING:
|
35
33
|
from collections.abc import Callable, Iterator, Sequence
|
36
34
|
from traceback import FrameSummary
|
37
35
|
from types import TracebackType
|
38
36
|
|
39
|
-
from utilities.types import
|
37
|
+
from utilities.types import (
|
38
|
+
MaybeCallablePathLike,
|
39
|
+
MaybeCallableZonedDateTime,
|
40
|
+
PathLike,
|
41
|
+
)
|
40
42
|
from utilities.version import MaybeCallableVersionLike
|
41
43
|
|
42
44
|
|
@@ -51,7 +53,7 @@ def format_exception_stack(
|
|
51
53
|
/,
|
52
54
|
*,
|
53
55
|
header: bool = False,
|
54
|
-
start:
|
56
|
+
start: MaybeCallableZonedDateTime | None = _START,
|
55
57
|
version: MaybeCallableVersionLike | None = None,
|
56
58
|
capture_locals: bool = False,
|
57
59
|
max_width: int = RICH_MAX_WIDTH,
|
@@ -82,21 +84,18 @@ def format_exception_stack(
|
|
82
84
|
|
83
85
|
def _yield_header_lines(
|
84
86
|
*,
|
85
|
-
start:
|
87
|
+
start: MaybeCallableZonedDateTime | None = _START,
|
86
88
|
version: MaybeCallableVersionLike | None = None,
|
87
89
|
) -> Iterator[str]:
|
88
90
|
"""Yield the header lines."""
|
89
|
-
now =
|
90
|
-
start_use =
|
91
|
-
|
92
|
-
|
93
|
-
)
|
94
|
-
yield f"Date/time | {serialize_zoned_datetime(now)}"
|
95
|
-
start_str = "" if start_use is None else serialize_zoned_datetime(start_use)
|
91
|
+
now = get_now()
|
92
|
+
start_use = to_zoned_date_time(date_time=start)
|
93
|
+
yield f"Date/time | {format_compact(now)}"
|
94
|
+
start_str = "" if start_use is None else format_compact(start_use)
|
96
95
|
yield f"Started | {start_str}"
|
97
|
-
|
98
|
-
|
99
|
-
yield f"Duration | {
|
96
|
+
delta = None if start_use is None else (now - start_use)
|
97
|
+
delta_str = "" if delta is None else delta.format_common_iso()
|
98
|
+
yield f"Duration | {delta_str}"
|
100
99
|
yield f"User | {getuser()}"
|
101
100
|
yield f"Host | {gethostname()}"
|
102
101
|
version_use = "" if version is None else get_version(version=version)
|
@@ -193,7 +192,7 @@ def _trim_path(path: PathLike, pattern: str, /) -> Path | None:
|
|
193
192
|
|
194
193
|
def make_except_hook(
|
195
194
|
*,
|
196
|
-
start:
|
195
|
+
start: MaybeCallableZonedDateTime | None = _START,
|
197
196
|
version: MaybeCallableVersionLike | None = None,
|
198
197
|
path: MaybeCallablePathLike | None = None,
|
199
198
|
max_width: int = RICH_MAX_WIDTH,
|
@@ -228,7 +227,7 @@ def _make_except_hook_inner(
|
|
228
227
|
traceback: TracebackType | None,
|
229
228
|
/,
|
230
229
|
*,
|
231
|
-
start:
|
230
|
+
start: MaybeCallableZonedDateTime | None = _START,
|
232
231
|
version: MaybeCallableVersionLike | None = None,
|
233
232
|
path: MaybeCallablePathLike | None = None,
|
234
233
|
max_width: int = RICH_MAX_WIDTH,
|
@@ -247,9 +246,7 @@ def _make_except_hook_inner(
|
|
247
246
|
_ = sys.stderr.write(f"{slim}\n") # don't 'from sys import stderr'
|
248
247
|
if path is not None:
|
249
248
|
path = (
|
250
|
-
get_path(path=path)
|
251
|
-
.joinpath(serialize_compact(get_now_local()))
|
252
|
-
.with_suffix(".txt")
|
249
|
+
get_path(path=path).joinpath(format_compact(get_now())).with_suffix(".txt")
|
253
250
|
)
|
254
251
|
full = format_exception_stack(
|
255
252
|
exc_val,
|
utilities/types.py
CHANGED
@@ -20,6 +20,16 @@ from typing import (
|
|
20
20
|
)
|
21
21
|
from zoneinfo import ZoneInfo
|
22
22
|
|
23
|
+
from whenever import (
|
24
|
+
Date,
|
25
|
+
DateDelta,
|
26
|
+
DateTimeDelta,
|
27
|
+
PlainDateTime,
|
28
|
+
Time,
|
29
|
+
TimeDelta,
|
30
|
+
ZonedDateTime,
|
31
|
+
)
|
32
|
+
|
23
33
|
_T_co = TypeVar("_T_co", covariant=True)
|
24
34
|
_T_contra = TypeVar("_T_contra", contravariant=True)
|
25
35
|
|
@@ -88,17 +98,16 @@ TDataclass = TypeVar("TDataclass", bound=Dataclass)
|
|
88
98
|
|
89
99
|
|
90
100
|
# datetime
|
91
|
-
type DateLike = MaybeStr[dt.date]
|
92
|
-
type DateTimeLike = MaybeStr[dt.datetime]
|
93
101
|
type DateOrDateTime = dt.date | dt.datetime
|
102
|
+
type DateTimeLike = MaybeStr[dt.datetime]
|
94
103
|
type Duration = Number | dt.timedelta
|
95
104
|
type DurationLike = MaybeStr[Duration]
|
96
105
|
type DurationOrEveryDuration = Duration | tuple[Literal["every"], Duration]
|
97
|
-
type
|
98
|
-
type
|
99
|
-
type
|
100
|
-
type
|
101
|
-
type
|
106
|
+
type MaybeCallablePyDate = MaybeCallable[dt.date]
|
107
|
+
type MaybeCallablePyDateTime = MaybeCallable[dt.datetime]
|
108
|
+
type PyDateLike = MaybeStr[dt.date]
|
109
|
+
type PyTimeDeltaLike = MaybeStr[dt.timedelta]
|
110
|
+
type PyTimeLike = MaybeStr[dt.time]
|
102
111
|
|
103
112
|
|
104
113
|
# enum
|
@@ -133,7 +142,7 @@ type LoggerOrName = MaybeStr[Logger]
|
|
133
142
|
|
134
143
|
# math
|
135
144
|
type Number = int | float
|
136
|
-
type
|
145
|
+
type MathRoundMode = Literal[
|
137
146
|
"standard",
|
138
147
|
"floor",
|
139
148
|
"ceil",
|
@@ -259,6 +268,25 @@ type ExcInfo = tuple[type[BaseException], BaseException, TracebackType]
|
|
259
268
|
type OptExcInfo = ExcInfo | tuple[None, None, None]
|
260
269
|
|
261
270
|
|
271
|
+
# whenever
|
272
|
+
type DateDeltaLike = MaybeStr[DateDelta]
|
273
|
+
type DateLike = MaybeStr[Date]
|
274
|
+
type DateTimeDeltaLike = MaybeStr[DateTimeDelta]
|
275
|
+
type MaybeCallableDate = MaybeCallable[Date]
|
276
|
+
type MaybeCallableZonedDateTime = MaybeCallable[ZonedDateTime]
|
277
|
+
type PlainDateTimeLike = MaybeStr[PlainDateTime]
|
278
|
+
type TimeDeltaLike = MaybeStr[TimeDelta]
|
279
|
+
type TimeLike = MaybeStr[Time]
|
280
|
+
type ZonedDateTimeLike = MaybeStr[ZonedDateTime]
|
281
|
+
type DateTimeRoundUnit = Literal[
|
282
|
+
"day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"
|
283
|
+
]
|
284
|
+
type DateTimeRoundMode = Literal[
|
285
|
+
"ceil", "floor", "half_ceil", "half_floor", "half_even"
|
286
|
+
]
|
287
|
+
type WeekDay = Literal["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
|
288
|
+
|
289
|
+
|
262
290
|
# zoneinfo
|
263
291
|
# fmt: off
|
264
292
|
type TimeZone = Literal[
|
@@ -271,9 +299,13 @@ type TimeZoneLike = ZoneInfo | Literal["local"] | TimeZone | dt.tzinfo | dt.date
|
|
271
299
|
__all__ = [
|
272
300
|
"Coroutine1",
|
273
301
|
"Dataclass",
|
302
|
+
"DateDeltaLike",
|
274
303
|
"DateLike",
|
275
304
|
"DateOrDateTime",
|
305
|
+
"DateTimeDeltaLike",
|
276
306
|
"DateTimeLike",
|
307
|
+
"DateTimeRoundMode",
|
308
|
+
"DateTimeRoundUnit",
|
277
309
|
"Duration",
|
278
310
|
"DurationLike",
|
279
311
|
"DurationOrEveryDuration",
|
@@ -282,12 +314,15 @@ __all__ = [
|
|
282
314
|
"IterableHashable",
|
283
315
|
"LogLevel",
|
284
316
|
"LoggerOrName",
|
317
|
+
"MathRoundMode",
|
285
318
|
"MaybeAwaitable",
|
286
319
|
"MaybeCallable",
|
287
320
|
"MaybeCallableDate",
|
288
|
-
"MaybeCallableDateTime",
|
289
321
|
"MaybeCallableEvent",
|
290
322
|
"MaybeCallablePathLike",
|
323
|
+
"MaybeCallablePyDate",
|
324
|
+
"MaybeCallablePyDateTime",
|
325
|
+
"MaybeCallableZonedDateTime",
|
291
326
|
"MaybeCoroutine1",
|
292
327
|
"MaybeIterable",
|
293
328
|
"MaybeIterableHashable",
|
@@ -300,7 +335,10 @@ __all__ = [
|
|
300
335
|
"ParseObjectExtra",
|
301
336
|
"PathLike",
|
302
337
|
"PatternLike",
|
303
|
-
"
|
338
|
+
"PlainDateTimeLike",
|
339
|
+
"PyDateLike",
|
340
|
+
"PyTimeDeltaLike",
|
341
|
+
"PyTimeLike",
|
304
342
|
"Seed",
|
305
343
|
"SerializeObjectExtra",
|
306
344
|
"Sign",
|
@@ -343,4 +381,5 @@ __all__ = [
|
|
343
381
|
"TupleOrStrMapping",
|
344
382
|
"TypeLike",
|
345
383
|
"WeekDay",
|
384
|
+
"ZonedDateTimeLike",
|
346
385
|
]
|
utilities/typing.py
CHANGED
@@ -25,6 +25,16 @@ from typing import get_type_hints as _get_type_hints
|
|
25
25
|
from uuid import UUID
|
26
26
|
from warnings import warn
|
27
27
|
|
28
|
+
from whenever import (
|
29
|
+
Date,
|
30
|
+
DateDelta,
|
31
|
+
DateTimeDelta,
|
32
|
+
PlainDateTime,
|
33
|
+
Time,
|
34
|
+
TimeDelta,
|
35
|
+
ZonedDateTime,
|
36
|
+
)
|
37
|
+
|
28
38
|
from utilities.iterables import unique_everseen
|
29
39
|
from utilities.sentinel import Sentinel
|
30
40
|
from utilities.types import StrMapping
|
@@ -133,7 +143,21 @@ def get_type_hints(
|
|
133
143
|
) -> dict[str, Any]:
|
134
144
|
"""Get the type hints of an object."""
|
135
145
|
result: dict[str, Any] = obj.__annotations__
|
136
|
-
_ = {
|
146
|
+
_ = {
|
147
|
+
Date,
|
148
|
+
DateDelta,
|
149
|
+
DateTimeDelta,
|
150
|
+
Literal,
|
151
|
+
Path,
|
152
|
+
PlainDateTime,
|
153
|
+
Sentinel,
|
154
|
+
StrMapping,
|
155
|
+
Time,
|
156
|
+
TimeDelta,
|
157
|
+
UUID,
|
158
|
+
ZonedDateTime,
|
159
|
+
dt,
|
160
|
+
}
|
137
161
|
globalns_use = globals() | ({} if globalns is None else dict(globalns))
|
138
162
|
localns_use = {} if localns is None else dict(localns)
|
139
163
|
try:
|
utilities/tzlocal.py
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
import datetime as dt
|
4
3
|
from logging import getLogger
|
5
4
|
from typing import TYPE_CHECKING
|
6
5
|
|
@@ -21,30 +20,7 @@ def get_local_time_zone() -> ZoneInfo:
|
|
21
20
|
|
22
21
|
|
23
22
|
LOCAL_TIME_ZONE = get_local_time_zone()
|
23
|
+
LOCAL_TIME_ZONE_NAME = LOCAL_TIME_ZONE.key
|
24
24
|
|
25
25
|
|
26
|
-
|
27
|
-
"""Get the current local time."""
|
28
|
-
return dt.datetime.now(tz=LOCAL_TIME_ZONE)
|
29
|
-
|
30
|
-
|
31
|
-
NOW_LOCAL = get_now_local()
|
32
|
-
|
33
|
-
|
34
|
-
def get_today_local() -> dt.date:
|
35
|
-
"""Get the current, timezone-aware local date."""
|
36
|
-
return get_now_local().date()
|
37
|
-
|
38
|
-
|
39
|
-
TODAY_LOCAL = get_today_local()
|
40
|
-
|
41
|
-
|
42
|
-
__all__ = [
|
43
|
-
"LOCAL_TIME_ZONE",
|
44
|
-
"LOCAL_TIME_ZONE",
|
45
|
-
"NOW_LOCAL",
|
46
|
-
"TODAY_LOCAL",
|
47
|
-
"get_local_time_zone",
|
48
|
-
"get_now_local",
|
49
|
-
"get_today_local",
|
50
|
-
]
|
26
|
+
__all__ = ["LOCAL_TIME_ZONE", "LOCAL_TIME_ZONE_NAME", "get_local_time_zone"]
|
utilities/whenever.py
CHANGED
@@ -34,12 +34,12 @@ from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
|
|
34
34
|
|
35
35
|
if TYPE_CHECKING:
|
36
36
|
from utilities.types import (
|
37
|
-
DateLike,
|
38
37
|
DateTimeLike,
|
39
38
|
Duration,
|
40
39
|
DurationLike,
|
41
|
-
|
42
|
-
|
40
|
+
PyDateLike,
|
41
|
+
PyTimeDeltaLike,
|
42
|
+
PyTimeLike,
|
43
43
|
)
|
44
44
|
|
45
45
|
|
@@ -91,7 +91,7 @@ class _CheckValidZonedDateTimeUnequalError(CheckValidZonedDateTimeError):
|
|
91
91
|
##
|
92
92
|
|
93
93
|
|
94
|
-
def ensure_date(date:
|
94
|
+
def ensure_date(date: PyDateLike, /) -> dt.date:
|
95
95
|
"""Ensure the object is a date."""
|
96
96
|
if isinstance(date, dt.date):
|
97
97
|
check_date_not_datetime(date)
|
@@ -180,7 +180,7 @@ class EnsurePlainDateTimeError(Exception):
|
|
180
180
|
##
|
181
181
|
|
182
182
|
|
183
|
-
def ensure_time(time:
|
183
|
+
def ensure_time(time: PyTimeLike, /) -> dt.time:
|
184
184
|
"""Ensure the object is a time."""
|
185
185
|
if isinstance(time, dt.time):
|
186
186
|
return time
|
@@ -202,7 +202,7 @@ class EnsureTimeError(Exception):
|
|
202
202
|
##
|
203
203
|
|
204
204
|
|
205
|
-
def ensure_timedelta(timedelta:
|
205
|
+
def ensure_timedelta(timedelta: PyTimeDeltaLike, /) -> dt.timedelta:
|
206
206
|
"""Ensure the object is a timedelta."""
|
207
207
|
if isinstance(timedelta, dt.timedelta):
|
208
208
|
return timedelta
|