dycw-utilities 0.131.11__py3-none-any.whl → 0.131.13__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.11.dist-info → dycw_utilities-0.131.13.dist-info}/METADATA +1 -1
- {dycw_utilities-0.131.11.dist-info → dycw_utilities-0.131.13.dist-info}/RECORD +21 -21
- utilities/__init__.py +1 -1
- utilities/atools.py +7 -9
- utilities/cachetools.py +8 -10
- utilities/datetime.py +2 -9
- utilities/fastapi.py +2 -4
- utilities/fpdf2.py +2 -2
- utilities/hypothesis.py +0 -136
- utilities/logging.py +54 -52
- utilities/orjson.py +46 -45
- utilities/period.py +86 -256
- utilities/pyinstrument.py +2 -3
- utilities/traceback.py +18 -21
- utilities/typing.py +25 -1
- utilities/tzdata.py +1 -53
- utilities/tzlocal.py +2 -26
- utilities/whenever2.py +15 -2
- utilities/zoneinfo.py +2 -2
- {dycw_utilities-0.131.11.dist-info → dycw_utilities-0.131.13.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.131.11.dist-info → dycw_utilities-0.131.13.dist-info}/licenses/LICENSE +0 -0
utilities/period.py
CHANGED
@@ -1,42 +1,20 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
|
4
|
-
from
|
5
|
-
from
|
6
|
-
|
7
|
-
from
|
8
|
-
|
9
|
-
|
10
|
-
Literal,
|
11
|
-
Self,
|
12
|
-
TypedDict,
|
13
|
-
TypeVar,
|
14
|
-
assert_never,
|
15
|
-
cast,
|
16
|
-
override,
|
17
|
-
)
|
18
|
-
|
19
|
-
from utilities.datetime import ZERO_TIME
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import TYPE_CHECKING, Generic, Self, TypedDict, TypeVar, override
|
5
|
+
from zoneinfo import ZoneInfo
|
6
|
+
|
7
|
+
from whenever import Date, DateDelta, TimeDelta, ZonedDateTime
|
8
|
+
|
9
|
+
from utilities.dataclasses import replace_non_sentinel
|
20
10
|
from utilities.functions import get_class_name
|
21
|
-
from utilities.iterables import OneUniqueNonUniqueError, always_iterable, one_unique
|
22
11
|
from utilities.sentinel import Sentinel, sentinel
|
23
|
-
from utilities.
|
24
|
-
from utilities.whenever import (
|
25
|
-
serialize_date,
|
26
|
-
serialize_plain_datetime,
|
27
|
-
serialize_zoned_datetime,
|
28
|
-
)
|
29
|
-
from utilities.zoneinfo import EnsureTimeZoneError, ensure_time_zone
|
12
|
+
from utilities.zoneinfo import get_time_zone_name
|
30
13
|
|
31
14
|
if TYPE_CHECKING:
|
32
|
-
from
|
33
|
-
|
34
|
-
from utilities.iterables import MaybeIterable
|
35
|
-
from utilities.types import DateOrDateTime
|
15
|
+
from utilities.types import TimeZoneLike
|
36
16
|
|
37
|
-
|
38
|
-
type _DateOrDateTime = Literal["date", "datetime"]
|
39
|
-
_TPeriod = TypeVar("_TPeriod", dt.date, dt.datetime)
|
17
|
+
_TPeriod = TypeVar("_TPeriod", Date, ZonedDateTime)
|
40
18
|
|
41
19
|
|
42
20
|
class _PeriodAsDict(TypedDict, Generic[_TPeriod]):
|
@@ -44,281 +22,133 @@ class _PeriodAsDict(TypedDict, Generic[_TPeriod]):
|
|
44
22
|
end: _TPeriod
|
45
23
|
|
46
24
|
|
47
|
-
@dataclass(repr=False, order=True, unsafe_hash=True)
|
48
|
-
class
|
49
|
-
"""A period of
|
25
|
+
@dataclass(repr=False, order=True, unsafe_hash=True, kw_only=False)
|
26
|
+
class DatePeriod:
|
27
|
+
"""A period of dates."""
|
50
28
|
|
51
|
-
start:
|
52
|
-
end:
|
53
|
-
req_duration: MaybeIterable[dt.timedelta] | None = field(
|
54
|
-
default=None, repr=False, kw_only=True
|
55
|
-
)
|
56
|
-
min_duration: dt.timedelta | None = field(default=None, repr=False, kw_only=True)
|
57
|
-
max_duration: dt.timedelta | None = field(default=None, repr=False, kw_only=True)
|
29
|
+
start: Date
|
30
|
+
end: Date
|
58
31
|
|
59
32
|
def __post_init__(self) -> None:
|
60
|
-
if
|
61
|
-
is_instance_gen(left, cls) is not is_instance_gen(right, cls)
|
62
|
-
for left, right in permutations([self.start, self.end], 2)
|
63
|
-
for cls in [dt.date, dt.datetime]
|
64
|
-
):
|
65
|
-
raise _PeriodDateAndDateTimeMixedError(start=self.start, end=self.end)
|
66
|
-
for date in [self.start, self.end]:
|
67
|
-
if isinstance(date, dt.datetime):
|
68
|
-
try:
|
69
|
-
_ = ensure_time_zone(date)
|
70
|
-
except EnsureTimeZoneError:
|
71
|
-
raise _PeriodNaiveDateTimeError(
|
72
|
-
start=self.start, end=self.end
|
73
|
-
) from None
|
74
|
-
duration = self.end - self.start
|
75
|
-
if duration < ZERO_TIME:
|
33
|
+
if self.start > self.end:
|
76
34
|
raise _PeriodInvalidError(start=self.start, end=self.end)
|
77
|
-
if (self.req_duration is not None) and (
|
78
|
-
duration not in always_iterable(self.req_duration)
|
79
|
-
):
|
80
|
-
raise _PeriodReqDurationError(
|
81
|
-
start=self.start,
|
82
|
-
end=self.end,
|
83
|
-
duration=duration,
|
84
|
-
req_duration=self.req_duration,
|
85
|
-
)
|
86
|
-
if (self.min_duration is not None) and (duration < self.min_duration):
|
87
|
-
raise _PeriodMinDurationError(
|
88
|
-
start=self.start,
|
89
|
-
end=self.end,
|
90
|
-
duration=duration,
|
91
|
-
min_duration=self.min_duration,
|
92
|
-
)
|
93
|
-
if (self.max_duration is not None) and (duration > self.max_duration):
|
94
|
-
raise _PeriodMaxDurationError(
|
95
|
-
start=self.start,
|
96
|
-
end=self.end,
|
97
|
-
duration=duration,
|
98
|
-
max_duration=self.max_duration,
|
99
|
-
)
|
100
35
|
|
101
|
-
def __add__(self, other:
|
36
|
+
def __add__(self, other: DateDelta, /) -> Self:
|
102
37
|
"""Offset the period."""
|
103
38
|
return self.replace(start=self.start + other, end=self.end + other)
|
104
39
|
|
105
|
-
def __contains__(self, other:
|
40
|
+
def __contains__(self, other: Date, /) -> bool:
|
106
41
|
"""Check if a date/datetime lies in the period."""
|
107
|
-
match self.kind:
|
108
|
-
case "date":
|
109
|
-
if isinstance(other, dt.datetime):
|
110
|
-
raise _PeriodDateContainsDateTimeError(
|
111
|
-
start=self.start, end=self.end
|
112
|
-
)
|
113
|
-
case "datetime":
|
114
|
-
if not isinstance(other, dt.datetime):
|
115
|
-
raise _PeriodDateTimeContainsDateError(
|
116
|
-
start=self.start, end=self.end
|
117
|
-
)
|
118
|
-
case _ as never:
|
119
|
-
assert_never(never)
|
120
42
|
return self.start <= other <= self.end
|
121
43
|
|
122
44
|
@override
|
123
45
|
def __repr__(self) -> str:
|
124
46
|
cls = get_class_name(self)
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
start, end = map(serialize_date, [result.start, result.end])
|
129
|
-
return f"{cls}({start}, {end})"
|
130
|
-
case "datetime":
|
131
|
-
result = cast("Period[dt.datetime]", self)
|
132
|
-
try:
|
133
|
-
time_zone = result.time_zone
|
134
|
-
except _PeriodTimeZoneNonUniqueError:
|
135
|
-
start, end = map(
|
136
|
-
serialize_zoned_datetime, [result.start, result.end]
|
137
|
-
)
|
138
|
-
return f"{cls}({start}, {end})"
|
139
|
-
start, end = (
|
140
|
-
serialize_plain_datetime(t.replace(tzinfo=None))
|
141
|
-
for t in [result.start, result.end]
|
142
|
-
)
|
143
|
-
return f"{cls}({start}, {end}, {time_zone})"
|
144
|
-
case _ as never:
|
145
|
-
assert_never(never)
|
146
|
-
|
147
|
-
def __sub__(self, other: dt.timedelta, /) -> Self:
|
47
|
+
return f"{cls}({self.start}, {self.end})"
|
48
|
+
|
49
|
+
def __sub__(self, other: DateDelta, /) -> Self:
|
148
50
|
"""Offset the period."""
|
149
51
|
return self.replace(start=self.start - other, end=self.end - other)
|
150
52
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
case "date":
|
155
|
-
raise _PeriodAsTimeZoneInapplicableError(start=self.start, end=self.end)
|
156
|
-
case "datetime":
|
157
|
-
result = cast("Period[dt.datetime]", self)
|
158
|
-
result = result.replace(
|
159
|
-
start=result.start.astimezone(time_zone),
|
160
|
-
end=result.end.astimezone(time_zone),
|
161
|
-
)
|
162
|
-
return cast("Self", result)
|
163
|
-
case _ as never:
|
164
|
-
assert_never(never)
|
165
|
-
|
166
|
-
@cached_property
|
167
|
-
def duration(self) -> dt.timedelta:
|
168
|
-
"""The duration of the period."""
|
53
|
+
@property
|
54
|
+
def delta(self) -> DateDelta:
|
55
|
+
"""The delta of the period."""
|
169
56
|
return self.end - self.start
|
170
57
|
|
171
|
-
@cached_property
|
172
|
-
def kind(self) -> _DateOrDateTime:
|
173
|
-
"""The kind of the period."""
|
174
|
-
return "date" if is_instance_gen(self.start, dt.date) else "datetime"
|
175
|
-
|
176
58
|
def replace(
|
177
|
-
self,
|
178
|
-
*,
|
179
|
-
start: _TPeriod | None = None,
|
180
|
-
end: _TPeriod | None = None,
|
181
|
-
req_duration: MaybeIterable[dt.timedelta] | None | Sentinel = sentinel,
|
182
|
-
min_duration: dt.timedelta | None | Sentinel = sentinel,
|
183
|
-
max_duration: dt.timedelta | None | Sentinel = sentinel,
|
59
|
+
self, *, start: Date | Sentinel = sentinel, end: Date | Sentinel = sentinel
|
184
60
|
) -> Self:
|
185
61
|
"""Replace elements of the period."""
|
186
|
-
return
|
187
|
-
self.start if start is None else start,
|
188
|
-
self.end if end is None else end,
|
189
|
-
req_duration=self.req_duration
|
190
|
-
if isinstance(req_duration, Sentinel)
|
191
|
-
else req_duration,
|
192
|
-
min_duration=self.min_duration
|
193
|
-
if isinstance(min_duration, Sentinel)
|
194
|
-
else min_duration,
|
195
|
-
max_duration=self.max_duration
|
196
|
-
if isinstance(max_duration, Sentinel)
|
197
|
-
else max_duration,
|
198
|
-
)
|
199
|
-
|
200
|
-
@cached_property
|
201
|
-
def time_zone(self) -> ZoneInfo:
|
202
|
-
"""The time zone of the period."""
|
203
|
-
match self.kind:
|
204
|
-
case "date":
|
205
|
-
raise _PeriodTimeZoneInapplicableError(
|
206
|
-
start=self.start, end=self.end
|
207
|
-
) from None
|
208
|
-
case "datetime":
|
209
|
-
result = cast("Period[dt.datetime]", self)
|
210
|
-
try:
|
211
|
-
return one_unique(map(ensure_time_zone, [result.start, result.end]))
|
212
|
-
except OneUniqueNonUniqueError as error:
|
213
|
-
raise _PeriodTimeZoneNonUniqueError(
|
214
|
-
start=self.start,
|
215
|
-
end=self.end,
|
216
|
-
first=error.first,
|
217
|
-
second=error.second,
|
218
|
-
) from None
|
219
|
-
case _ as never:
|
220
|
-
assert_never(never)
|
221
|
-
|
222
|
-
def to_dict(self) -> _PeriodAsDict:
|
223
|
-
"""Convert the period to a dictionary."""
|
224
|
-
return {"start": self.start, "end": self.end}
|
225
|
-
|
226
|
-
|
227
|
-
@dataclass(kw_only=True, slots=True)
|
228
|
-
class PeriodError(Generic[_TPeriod], Exception):
|
229
|
-
start: _TPeriod
|
230
|
-
end: _TPeriod
|
62
|
+
return replace_non_sentinel(self, start=start, end=end)
|
231
63
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
@override
|
236
|
-
def __str__(self) -> str:
|
237
|
-
return f"Invalid period; got date and datetime mix ({self.start}, {self.end})"
|
64
|
+
def to_dict(self) -> _PeriodAsDict[Date]:
|
65
|
+
"""Convert the period to a dictionary."""
|
66
|
+
return _PeriodAsDict(start=self.start, end=self.end)
|
238
67
|
|
239
68
|
|
240
|
-
@dataclass(
|
241
|
-
class
|
242
|
-
|
243
|
-
def __str__(self) -> str:
|
244
|
-
return f"Invalid period; got naive datetime(s) ({self.start}, {self.end})"
|
69
|
+
@dataclass(repr=False, order=True, unsafe_hash=True, kw_only=False)
|
70
|
+
class ZonedDateTimePeriod:
|
71
|
+
"""A period of time."""
|
245
72
|
|
73
|
+
start: ZonedDateTime
|
74
|
+
end: ZonedDateTime
|
246
75
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
76
|
+
def __post_init__(self) -> None:
|
77
|
+
if self.start > self.end:
|
78
|
+
raise _PeriodInvalidError(start=self.start, end=self.end)
|
79
|
+
if self.start.tz != self.end.tz:
|
80
|
+
raise _PeriodTimeZoneError(
|
81
|
+
start=ZoneInfo(self.start.tz), end=ZoneInfo(self.end.tz)
|
82
|
+
)
|
252
83
|
|
84
|
+
def __add__(self, other: TimeDelta, /) -> Self:
|
85
|
+
"""Offset the period."""
|
86
|
+
return self.replace(start=self.start + other, end=self.end + other)
|
253
87
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
req_duration: MaybeIterable[dt.timedelta]
|
88
|
+
def __contains__(self, other: ZonedDateTime, /) -> bool:
|
89
|
+
"""Check if a date/datetime lies in the period."""
|
90
|
+
return self.start <= other <= self.end
|
258
91
|
|
259
92
|
@override
|
260
|
-
def
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
@dataclass(kw_only=True, slots=True)
|
265
|
-
class _PeriodMinDurationError(PeriodError[_TPeriod]):
|
266
|
-
duration: dt.timedelta
|
267
|
-
min_duration: dt.timedelta
|
93
|
+
def __repr__(self) -> str:
|
94
|
+
cls = get_class_name(self)
|
95
|
+
return f"{cls}({self.start.to_plain()}, {self.end.to_plain()}[{self.time_zone.key}])"
|
268
96
|
|
269
|
-
|
270
|
-
|
271
|
-
return (
|
272
|
-
f"Period must have min duration {self.min_duration}; got {self.duration})"
|
273
|
-
)
|
97
|
+
def __sub__(self, other: TimeDelta, /) -> Self:
|
98
|
+
"""Offset the period."""
|
99
|
+
return self.replace(start=self.start - other, end=self.end - other)
|
274
100
|
|
101
|
+
@property
|
102
|
+
def delta(self) -> TimeDelta:
|
103
|
+
"""The duration of the period."""
|
104
|
+
return self.end - self.start
|
275
105
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
106
|
+
def replace(
|
107
|
+
self,
|
108
|
+
*,
|
109
|
+
start: ZonedDateTime | Sentinel = sentinel,
|
110
|
+
end: ZonedDateTime | Sentinel = sentinel,
|
111
|
+
) -> Self:
|
112
|
+
"""Replace elements of the period."""
|
113
|
+
return replace_non_sentinel(self, start=start, end=end)
|
280
114
|
|
281
|
-
@
|
282
|
-
def
|
283
|
-
|
115
|
+
@property
|
116
|
+
def time_zone(self) -> ZoneInfo:
|
117
|
+
"""The time zone of the period."""
|
118
|
+
return ZoneInfo(self.start.tz)
|
284
119
|
|
120
|
+
def to_dict(self) -> _PeriodAsDict[ZonedDateTime]:
|
121
|
+
"""Convert the period to a dictionary."""
|
122
|
+
return _PeriodAsDict(start=self.start, end=self.end)
|
285
123
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
return "Period of dates does not have a timezone attribute"
|
124
|
+
def to_tz(self, time_zone: TimeZoneLike, /) -> Self:
|
125
|
+
"""Convert the time zone."""
|
126
|
+
tz = get_time_zone_name(time_zone)
|
127
|
+
return self.replace(start=self.start.to_tz(tz), end=self.end.to_tz(tz))
|
291
128
|
|
292
129
|
|
293
130
|
@dataclass(kw_only=True, slots=True)
|
294
|
-
class
|
295
|
-
@override
|
296
|
-
def __str__(self) -> str:
|
297
|
-
return "Period of dates cannot contain datetimes"
|
131
|
+
class PeriodError(Exception): ...
|
298
132
|
|
299
133
|
|
300
134
|
@dataclass(kw_only=True, slots=True)
|
301
|
-
class
|
302
|
-
|
303
|
-
|
304
|
-
return "Period of datetimes cannot contain dates"
|
305
|
-
|
135
|
+
class _PeriodInvalidError(PeriodError, Generic[_TPeriod]):
|
136
|
+
start: _TPeriod
|
137
|
+
end: _TPeriod
|
306
138
|
|
307
|
-
@dataclass(kw_only=True, slots=True)
|
308
|
-
class _PeriodTimeZoneInapplicableError(PeriodError[_TPeriod]):
|
309
139
|
@override
|
310
140
|
def __str__(self) -> str:
|
311
|
-
return "
|
141
|
+
return f"Invalid period; got {self.start} > {self.end}"
|
312
142
|
|
313
143
|
|
314
144
|
@dataclass(kw_only=True, slots=True)
|
315
|
-
class
|
316
|
-
|
317
|
-
|
145
|
+
class _PeriodTimeZoneError(PeriodError):
|
146
|
+
start: ZoneInfo
|
147
|
+
end: ZoneInfo
|
318
148
|
|
319
149
|
@override
|
320
150
|
def __str__(self) -> str:
|
321
|
-
return f"Period must contain exactly one time zone; got {self.
|
151
|
+
return f"Period must contain exactly one time zone; got {self.start} and {self.end}"
|
322
152
|
|
323
153
|
|
324
|
-
__all__ = ["
|
154
|
+
__all__ = ["DatePeriod", "PeriodError", "ZonedDateTimePeriod"]
|
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/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/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/tzdata.py
CHANGED
@@ -1,63 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import TYPE_CHECKING
|
4
3
|
from zoneinfo import ZoneInfo
|
5
4
|
|
6
|
-
from utilities.datetime import get_now, get_today
|
7
|
-
|
8
|
-
if TYPE_CHECKING:
|
9
|
-
import datetime as dt
|
10
|
-
|
11
|
-
|
12
5
|
HongKong = ZoneInfo("Asia/Hong_Kong")
|
13
6
|
Tokyo = ZoneInfo("Asia/Tokyo")
|
14
7
|
USCentral = ZoneInfo("US/Central")
|
15
8
|
USEastern = ZoneInfo("US/Eastern")
|
16
9
|
|
17
10
|
|
18
|
-
|
19
|
-
"""Get the current time in Hong Kong."""
|
20
|
-
return get_now(time_zone=HongKong)
|
21
|
-
|
22
|
-
|
23
|
-
NOW_HONG_KONG = get_now_hong_kong()
|
24
|
-
|
25
|
-
|
26
|
-
def get_now_tokyo() -> dt.datetime:
|
27
|
-
"""Get the current time in Tokyo."""
|
28
|
-
return get_now(time_zone=Tokyo)
|
29
|
-
|
30
|
-
|
31
|
-
NOW_TOKYO = get_now_tokyo()
|
32
|
-
|
33
|
-
|
34
|
-
def get_today_hong_kong() -> dt.date:
|
35
|
-
"""Get the current date in Hong Kong."""
|
36
|
-
return get_today(time_zone=HongKong)
|
37
|
-
|
38
|
-
|
39
|
-
TODAY_HONG_KONG = get_today_hong_kong()
|
40
|
-
|
41
|
-
|
42
|
-
def get_today_tokyo() -> dt.date:
|
43
|
-
"""Get the current date in Tokyo."""
|
44
|
-
return get_today(time_zone=Tokyo)
|
45
|
-
|
46
|
-
|
47
|
-
TODAY_TOKYO = get_today_tokyo()
|
48
|
-
|
49
|
-
|
50
|
-
__all__ = [
|
51
|
-
"NOW_HONG_KONG",
|
52
|
-
"NOW_TOKYO",
|
53
|
-
"TODAY_HONG_KONG",
|
54
|
-
"TODAY_TOKYO",
|
55
|
-
"HongKong",
|
56
|
-
"Tokyo",
|
57
|
-
"USCentral",
|
58
|
-
"USEastern",
|
59
|
-
"get_now_hong_kong",
|
60
|
-
"get_now_tokyo",
|
61
|
-
"get_today_hong_kong",
|
62
|
-
"get_today_tokyo",
|
63
|
-
]
|
11
|
+
__all__ = ["HongKong", "Tokyo", "USCentral", "USEastern"]
|
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"]
|