dycw-utilities 0.127.0__py3-none-any.whl → 0.129.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.127.0.dist-info → dycw_utilities-0.129.13.dist-info}/METADATA +16 -10
- {dycw_utilities-0.127.0.dist-info → dycw_utilities-0.129.13.dist-info}/RECORD +24 -23
- utilities/__init__.py +1 -1
- utilities/aiolimiter.py +25 -0
- utilities/asyncio.py +58 -411
- utilities/datetime.py +0 -8
- utilities/fastapi.py +26 -12
- utilities/git.py +5 -58
- utilities/hypothesis.py +1 -11
- utilities/logging.py +69 -56
- utilities/pathlib.py +83 -13
- utilities/pyinstrument.py +6 -4
- utilities/pytest_regressions.py +2 -2
- utilities/python_dotenv.py +10 -6
- utilities/redis.py +4 -43
- utilities/scipy.py +1 -1
- utilities/slack_sdk.py +2 -45
- utilities/sqlalchemy.py +2 -56
- utilities/traceback.py +278 -12
- utilities/types.py +2 -2
- utilities/version.py +0 -8
- utilities/whenever.py +64 -1
- {dycw_utilities-0.127.0.dist-info → dycw_utilities-0.129.13.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.127.0.dist-info → dycw_utilities-0.129.13.dist-info}/licenses/LICENSE +0 -0
utilities/traceback.py
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import re
|
4
|
+
import sys
|
5
|
+
from asyncio import run
|
3
6
|
from collections.abc import Callable, Iterable
|
4
7
|
from dataclasses import dataclass, field, replace
|
5
|
-
from functools import wraps
|
8
|
+
from functools import partial, wraps
|
6
9
|
from getpass import getuser
|
7
10
|
from inspect import iscoroutinefunction, signature
|
11
|
+
from itertools import repeat
|
8
12
|
from logging import Formatter, Handler, LogRecord
|
9
13
|
from pathlib import Path
|
10
14
|
from socket import gethostname
|
@@ -26,7 +30,8 @@ from typing import (
|
|
26
30
|
runtime_checkable,
|
27
31
|
)
|
28
32
|
|
29
|
-
from utilities.
|
33
|
+
from utilities.datetime import get_datetime, get_now, serialize_compact
|
34
|
+
from utilities.errors import ImpossibleCaseError, repr_error
|
30
35
|
from utilities.functions import (
|
31
36
|
ensure_not_none,
|
32
37
|
ensure_str,
|
@@ -34,7 +39,8 @@ from utilities.functions import (
|
|
34
39
|
get_func_name,
|
35
40
|
get_func_qualname,
|
36
41
|
)
|
37
|
-
from utilities.iterables import always_iterable, one
|
42
|
+
from utilities.iterables import OneEmptyError, always_iterable, one
|
43
|
+
from utilities.pathlib import get_path
|
38
44
|
from utilities.reprlib import (
|
39
45
|
RICH_EXPAND_ALL,
|
40
46
|
RICH_INDENT_SIZE,
|
@@ -45,11 +51,18 @@ from utilities.reprlib import (
|
|
45
51
|
yield_call_args_repr,
|
46
52
|
yield_mapping_repr,
|
47
53
|
)
|
48
|
-
from utilities.types import
|
54
|
+
from utilities.types import (
|
55
|
+
MaybeCallableDateTime,
|
56
|
+
MaybeCallablePathLike,
|
57
|
+
PathLike,
|
58
|
+
TBaseException,
|
59
|
+
TCallable,
|
60
|
+
)
|
49
61
|
from utilities.version import get_version
|
62
|
+
from utilities.whenever import serialize_duration
|
50
63
|
|
51
64
|
if TYPE_CHECKING:
|
52
|
-
from collections.abc import Callable, Iterable, Iterator
|
65
|
+
from collections.abc import Callable, Iterable, Iterator, Sequence
|
53
66
|
from logging import _FormatStyle
|
54
67
|
from types import FrameType, TracebackType
|
55
68
|
|
@@ -60,6 +73,132 @@ if TYPE_CHECKING:
|
|
60
73
|
_T = TypeVar("_T")
|
61
74
|
_CALL_ARGS = "_CALL_ARGS"
|
62
75
|
_INDENT = 4 * " "
|
76
|
+
_START = get_now()
|
77
|
+
|
78
|
+
|
79
|
+
##
|
80
|
+
|
81
|
+
|
82
|
+
def format_exception_stack(
|
83
|
+
error: BaseException,
|
84
|
+
/,
|
85
|
+
*,
|
86
|
+
header: bool = False,
|
87
|
+
start: MaybeCallableDateTime | None = _START,
|
88
|
+
version: MaybeCallableVersionLike | None = None,
|
89
|
+
capture_locals: bool = False,
|
90
|
+
max_width: int = RICH_MAX_WIDTH,
|
91
|
+
indent_size: int = RICH_INDENT_SIZE,
|
92
|
+
max_length: int | None = RICH_MAX_LENGTH,
|
93
|
+
max_string: int | None = RICH_MAX_STRING,
|
94
|
+
max_depth: int | None = RICH_MAX_DEPTH,
|
95
|
+
expand_all: bool = RICH_EXPAND_ALL,
|
96
|
+
) -> str:
|
97
|
+
"""Format an exception stack."""
|
98
|
+
lines: Sequence[str] = []
|
99
|
+
if header:
|
100
|
+
lines.extend(_yield_header_lines(start=start, version=version))
|
101
|
+
lines.extend(
|
102
|
+
_yield_formatted_frame_summary(
|
103
|
+
error,
|
104
|
+
capture_locals=capture_locals,
|
105
|
+
max_width=max_width,
|
106
|
+
indent_size=indent_size,
|
107
|
+
max_length=max_length,
|
108
|
+
max_string=max_string,
|
109
|
+
max_depth=max_depth,
|
110
|
+
expand_all=expand_all,
|
111
|
+
)
|
112
|
+
)
|
113
|
+
return "\n".join(lines)
|
114
|
+
|
115
|
+
|
116
|
+
##
|
117
|
+
|
118
|
+
|
119
|
+
def make_except_hook(
|
120
|
+
*,
|
121
|
+
start: MaybeCallableDateTime | None = _START,
|
122
|
+
version: MaybeCallableVersionLike | None = None,
|
123
|
+
path: MaybeCallablePathLike | None = None,
|
124
|
+
max_width: int = RICH_MAX_WIDTH,
|
125
|
+
indent_size: int = RICH_INDENT_SIZE,
|
126
|
+
max_length: int | None = RICH_MAX_LENGTH,
|
127
|
+
max_string: int | None = RICH_MAX_STRING,
|
128
|
+
max_depth: int | None = RICH_MAX_DEPTH,
|
129
|
+
expand_all: bool = RICH_EXPAND_ALL,
|
130
|
+
slack_url: str | None = None,
|
131
|
+
) -> Callable[
|
132
|
+
[type[BaseException] | None, BaseException | None, TracebackType | None], None
|
133
|
+
]:
|
134
|
+
"""Exception hook to log the traceback."""
|
135
|
+
return partial(
|
136
|
+
_make_except_hook_inner,
|
137
|
+
start=start,
|
138
|
+
version=version,
|
139
|
+
path=path,
|
140
|
+
max_width=max_width,
|
141
|
+
indent_size=indent_size,
|
142
|
+
max_length=max_length,
|
143
|
+
max_string=max_string,
|
144
|
+
max_depth=max_depth,
|
145
|
+
expand_all=expand_all,
|
146
|
+
slack_url=slack_url,
|
147
|
+
)
|
148
|
+
|
149
|
+
|
150
|
+
def _make_except_hook_inner(
|
151
|
+
exc_type: type[BaseException] | None,
|
152
|
+
exc_val: BaseException | None,
|
153
|
+
traceback: TracebackType | None,
|
154
|
+
/,
|
155
|
+
*,
|
156
|
+
start: MaybeCallableDateTime | None = _START,
|
157
|
+
version: MaybeCallableVersionLike | None = None,
|
158
|
+
path: MaybeCallablePathLike | None = None,
|
159
|
+
max_width: int = RICH_MAX_WIDTH,
|
160
|
+
indent_size: int = RICH_INDENT_SIZE,
|
161
|
+
max_length: int | None = RICH_MAX_LENGTH,
|
162
|
+
max_string: int | None = RICH_MAX_STRING,
|
163
|
+
max_depth: int | None = RICH_MAX_DEPTH,
|
164
|
+
expand_all: bool = RICH_EXPAND_ALL,
|
165
|
+
slack_url: str | None = None,
|
166
|
+
) -> None:
|
167
|
+
"""Exception hook to log the traceback."""
|
168
|
+
_ = (exc_type, traceback)
|
169
|
+
if exc_val is None:
|
170
|
+
raise MakeExceptHookError
|
171
|
+
slim = format_exception_stack(exc_val, header=True, start=start, version=version)
|
172
|
+
_ = sys.stderr.write(f"{slim}\n") # don't 'from sys import stderr'
|
173
|
+
if path is not None:
|
174
|
+
from utilities.atomicwrites import writer
|
175
|
+
from utilities.tzlocal import get_now_local
|
176
|
+
|
177
|
+
path = (
|
178
|
+
get_path(path=path)
|
179
|
+
.joinpath(serialize_compact(get_now_local()))
|
180
|
+
.with_suffix(".txt")
|
181
|
+
)
|
182
|
+
full = format_exception_stack(
|
183
|
+
exc_val,
|
184
|
+
header=True,
|
185
|
+
start=start,
|
186
|
+
version=version,
|
187
|
+
capture_locals=True,
|
188
|
+
max_width=max_width,
|
189
|
+
indent_size=indent_size,
|
190
|
+
max_length=max_length,
|
191
|
+
max_string=max_string,
|
192
|
+
max_depth=max_depth,
|
193
|
+
expand_all=expand_all,
|
194
|
+
)
|
195
|
+
with writer(path, overwrite=True) as temp:
|
196
|
+
_ = temp.write_text(full)
|
197
|
+
if slack_url is not None: # pragma: no cover
|
198
|
+
from utilities.slack_sdk import send_to_slack
|
199
|
+
|
200
|
+
send = f"```{slim}```"
|
201
|
+
run(send_to_slack(slack_url, send))
|
63
202
|
|
64
203
|
|
65
204
|
##
|
@@ -78,6 +217,7 @@ class RichTracebackFormatter(Formatter):
|
|
78
217
|
/,
|
79
218
|
*,
|
80
219
|
defaults: StrMapping | None = None,
|
220
|
+
start: MaybeCallableDateTime | None = _START,
|
81
221
|
version: MaybeCallableVersionLike | None = None,
|
82
222
|
max_width: int = RICH_MAX_WIDTH,
|
83
223
|
indent_size: int = RICH_INDENT_SIZE,
|
@@ -89,7 +229,8 @@ class RichTracebackFormatter(Formatter):
|
|
89
229
|
post: Callable[[str], str] | None = None,
|
90
230
|
) -> None:
|
91
231
|
super().__init__(fmt, datefmt, style, validate, defaults=defaults)
|
92
|
-
self.
|
232
|
+
self._start = get_datetime(datetime=start)
|
233
|
+
self._version = get_version(version=version)
|
93
234
|
self._max_width = max_width
|
94
235
|
self._indent_size = indent_size
|
95
236
|
self._max_length = max_length
|
@@ -110,6 +251,7 @@ class RichTracebackFormatter(Formatter):
|
|
110
251
|
exc_value = ensure_not_none(exc_value, desc="exc_value")
|
111
252
|
error = get_rich_traceback(
|
112
253
|
exc_value,
|
254
|
+
start=self._start,
|
113
255
|
version=self._version,
|
114
256
|
max_width=self._max_width,
|
115
257
|
indent_size=self._indent_size,
|
@@ -263,6 +405,7 @@ class ExcChainTB(Generic[TBaseException]):
|
|
263
405
|
errors: list[
|
264
406
|
ExcGroupTB[TBaseException] | ExcTB[TBaseException] | TBaseException
|
265
407
|
] = field(default_factory=list)
|
408
|
+
start: MaybeCallableDateTime | None = field(default=_START, repr=False)
|
266
409
|
version: MaybeCallableVersionLike | None = field(default=None, repr=False)
|
267
410
|
max_width: int = RICH_MAX_WIDTH
|
268
411
|
indent_size: int = RICH_INDENT_SIZE
|
@@ -292,7 +435,7 @@ class ExcChainTB(Generic[TBaseException]):
|
|
292
435
|
"""Format the traceback."""
|
293
436
|
lines: list[str] = []
|
294
437
|
if header: # pragma: no cover
|
295
|
-
lines.extend(_yield_header_lines(version=self.version))
|
438
|
+
lines.extend(_yield_header_lines(start=self.start, version=self.version))
|
296
439
|
total = len(self.errors)
|
297
440
|
for i, errors in enumerate(self.errors, start=1):
|
298
441
|
lines.append(f"Exception chain {i}/{total}:")
|
@@ -315,6 +458,7 @@ class ExcGroupTB(Generic[TBaseException]):
|
|
315
458
|
errors: list[
|
316
459
|
ExcGroupTB[TBaseException] | ExcTB[TBaseException] | TBaseException
|
317
460
|
] = field(default_factory=list)
|
461
|
+
start: MaybeCallableDateTime | None = field(default=_START, repr=False)
|
318
462
|
version: MaybeCallableVersionLike | None = field(default=None, repr=False)
|
319
463
|
max_width: int = RICH_MAX_WIDTH
|
320
464
|
indent_size: int = RICH_INDENT_SIZE
|
@@ -333,7 +477,7 @@ class ExcGroupTB(Generic[TBaseException]):
|
|
333
477
|
"""Format the traceback."""
|
334
478
|
lines: list[str] = [] # skipif-ci
|
335
479
|
if header: # pragma: no cover
|
336
|
-
lines.extend(_yield_header_lines(version=self.version))
|
480
|
+
lines.extend(_yield_header_lines(start=self.start, version=self.version))
|
337
481
|
lines.append("Exception group:") # skipif-ci
|
338
482
|
match self.exc_group: # skipif-ci
|
339
483
|
case ExcTB() as exc_tb:
|
@@ -363,6 +507,7 @@ class ExcTB(Generic[TBaseException]):
|
|
363
507
|
|
364
508
|
frames: list[_Frame] = field(default_factory=list)
|
365
509
|
error: TBaseException
|
510
|
+
start: MaybeCallableDateTime | None = field(default=_START, repr=False)
|
366
511
|
version: MaybeCallableVersionLike | None = field(default=None, repr=False)
|
367
512
|
max_width: int = RICH_MAX_WIDTH
|
368
513
|
indent_size: int = RICH_INDENT_SIZE
|
@@ -391,7 +536,7 @@ class ExcTB(Generic[TBaseException]):
|
|
391
536
|
total = len(self)
|
392
537
|
lines: list[str] = []
|
393
538
|
if header: # pragma: no cover
|
394
|
-
lines.extend(_yield_header_lines(version=self.version))
|
539
|
+
lines.extend(_yield_header_lines(start=self.start, version=self.version))
|
395
540
|
for i, frame in enumerate(self.frames):
|
396
541
|
is_head = i < total - 1
|
397
542
|
lines.append(
|
@@ -485,6 +630,7 @@ def get_rich_traceback(
|
|
485
630
|
error: TBaseException,
|
486
631
|
/,
|
487
632
|
*,
|
633
|
+
start: MaybeCallableDateTime | None = _START,
|
488
634
|
version: MaybeCallableVersionLike | None = None,
|
489
635
|
max_width: int = RICH_MAX_WIDTH,
|
490
636
|
indent_size: int = RICH_INDENT_SIZE,
|
@@ -506,6 +652,7 @@ def get_rich_traceback(
|
|
506
652
|
err_recast = cast("TBaseException", err)
|
507
653
|
return _get_rich_traceback_non_chain(
|
508
654
|
err_recast,
|
655
|
+
start=start,
|
509
656
|
version=version,
|
510
657
|
max_width=max_width,
|
511
658
|
indent_size=indent_size,
|
@@ -520,6 +667,7 @@ def get_rich_traceback(
|
|
520
667
|
errors=[
|
521
668
|
_get_rich_traceback_non_chain(
|
522
669
|
e,
|
670
|
+
start=start,
|
523
671
|
version=version,
|
524
672
|
max_width=max_width,
|
525
673
|
indent_size=indent_size,
|
@@ -530,6 +678,7 @@ def get_rich_traceback(
|
|
530
678
|
)
|
531
679
|
for e in errs_recast
|
532
680
|
],
|
681
|
+
start=start,
|
533
682
|
version=version,
|
534
683
|
max_width=max_width,
|
535
684
|
indent_size=indent_size,
|
@@ -544,6 +693,7 @@ def _get_rich_traceback_non_chain(
|
|
544
693
|
error: ExceptionGroup[Any] | TBaseException,
|
545
694
|
/,
|
546
695
|
*,
|
696
|
+
start: MaybeCallableDateTime | None = _START,
|
547
697
|
version: MaybeCallableVersionLike | None = None,
|
548
698
|
max_width: int = RICH_MAX_WIDTH,
|
549
699
|
indent_size: int = RICH_INDENT_SIZE,
|
@@ -567,6 +717,7 @@ def _get_rich_traceback_non_chain(
|
|
567
717
|
errors = [
|
568
718
|
_get_rich_traceback_non_chain(
|
569
719
|
e,
|
720
|
+
start=start,
|
570
721
|
version=version,
|
571
722
|
max_width=max_width,
|
572
723
|
indent_size=indent_size,
|
@@ -580,6 +731,7 @@ def _get_rich_traceback_non_chain(
|
|
580
731
|
return ExcGroupTB(
|
581
732
|
exc_group=exc_group_or_exc_tb,
|
582
733
|
errors=errors,
|
734
|
+
start=start,
|
583
735
|
version=version,
|
584
736
|
max_width=max_width,
|
585
737
|
indent_size=indent_size,
|
@@ -591,6 +743,7 @@ def _get_rich_traceback_non_chain(
|
|
591
743
|
case BaseException() as base_exc:
|
592
744
|
return _get_rich_traceback_base_one(
|
593
745
|
base_exc,
|
746
|
+
start=start,
|
594
747
|
version=version,
|
595
748
|
max_width=max_width,
|
596
749
|
indent_size=indent_size,
|
@@ -607,6 +760,7 @@ def _get_rich_traceback_base_one(
|
|
607
760
|
error: TBaseException,
|
608
761
|
/,
|
609
762
|
*,
|
763
|
+
start: MaybeCallableDateTime | None = _START,
|
610
764
|
version: MaybeCallableVersionLike | None = None,
|
611
765
|
max_width: int = RICH_MAX_WIDTH,
|
612
766
|
indent_size: int = RICH_INDENT_SIZE,
|
@@ -638,6 +792,7 @@ def _get_rich_traceback_base_one(
|
|
638
792
|
return ExcTB(
|
639
793
|
frames=frames,
|
640
794
|
error=error,
|
795
|
+
start=start,
|
641
796
|
version=version,
|
642
797
|
max_width=max_width,
|
643
798
|
indent_size=indent_size,
|
@@ -792,14 +947,29 @@ def _merge_frames(
|
|
792
947
|
return values[::-1]
|
793
948
|
|
794
949
|
|
950
|
+
##
|
951
|
+
|
952
|
+
|
795
953
|
def _yield_header_lines(
|
796
|
-
*,
|
954
|
+
*,
|
955
|
+
start: MaybeCallableDateTime | None = _START,
|
956
|
+
version: MaybeCallableVersionLike | None = None,
|
797
957
|
) -> Iterator[str]:
|
798
958
|
"""Yield the header lines."""
|
799
|
-
from utilities.tzlocal import get_now_local
|
959
|
+
from utilities.tzlocal import get_local_time_zone, get_now_local
|
800
960
|
from utilities.whenever import serialize_zoned_datetime
|
801
961
|
|
802
|
-
|
962
|
+
now = get_now_local()
|
963
|
+
start_use = get_datetime(datetime=start)
|
964
|
+
start_use = (
|
965
|
+
None if start_use is None else start_use.astimezone(get_local_time_zone())
|
966
|
+
)
|
967
|
+
yield f"Date/time | {serialize_zoned_datetime(now)}"
|
968
|
+
start_str = "" if start_use is None else serialize_zoned_datetime(start_use)
|
969
|
+
yield f"Started | {start_str}"
|
970
|
+
duration = None if start_use is None else (now - start_use)
|
971
|
+
duration_str = "" if duration is None else serialize_duration(duration)
|
972
|
+
yield f"Duration | {duration_str}"
|
803
973
|
yield f"User | {getuser()}"
|
804
974
|
yield f"Host | {gethostname()}"
|
805
975
|
version_use = "" if version is None else get_version(version=version)
|
@@ -807,12 +977,108 @@ def _yield_header_lines(
|
|
807
977
|
yield ""
|
808
978
|
|
809
979
|
|
980
|
+
##
|
981
|
+
|
982
|
+
|
983
|
+
def _yield_formatted_frame_summary(
|
984
|
+
error: BaseException,
|
985
|
+
/,
|
986
|
+
*,
|
987
|
+
capture_locals: bool = False,
|
988
|
+
max_width: int = RICH_MAX_WIDTH,
|
989
|
+
indent_size: int = RICH_INDENT_SIZE,
|
990
|
+
max_length: int | None = RICH_MAX_LENGTH,
|
991
|
+
max_string: int | None = RICH_MAX_STRING,
|
992
|
+
max_depth: int | None = RICH_MAX_DEPTH,
|
993
|
+
expand_all: bool = RICH_EXPAND_ALL,
|
994
|
+
) -> Iterator[str]:
|
995
|
+
"""Yield the formatted frame summary lines."""
|
996
|
+
stack = TracebackException.from_exception(
|
997
|
+
error, capture_locals=capture_locals
|
998
|
+
).stack
|
999
|
+
n = len(stack)
|
1000
|
+
for i, frame in enumerate(stack, start=1):
|
1001
|
+
num = f"{i}/{n}"
|
1002
|
+
first, *rest = _yield_frame_summary_lines(
|
1003
|
+
frame,
|
1004
|
+
max_width=max_width,
|
1005
|
+
indent_size=indent_size,
|
1006
|
+
max_length=max_length,
|
1007
|
+
max_string=max_string,
|
1008
|
+
max_depth=max_depth,
|
1009
|
+
expand_all=expand_all,
|
1010
|
+
)
|
1011
|
+
yield f"{num} | {first}"
|
1012
|
+
blank = "".join(repeat(" ", len(num)))
|
1013
|
+
for rest_i in rest:
|
1014
|
+
yield f"{blank} | {rest_i}"
|
1015
|
+
yield repr_error(error)
|
1016
|
+
|
1017
|
+
|
1018
|
+
def _yield_frame_summary_lines(
|
1019
|
+
frame: FrameSummary,
|
1020
|
+
/,
|
1021
|
+
*,
|
1022
|
+
max_width: int = RICH_MAX_WIDTH,
|
1023
|
+
indent_size: int = RICH_INDENT_SIZE,
|
1024
|
+
max_length: int | None = RICH_MAX_LENGTH,
|
1025
|
+
max_string: int | None = RICH_MAX_STRING,
|
1026
|
+
max_depth: int | None = RICH_MAX_DEPTH,
|
1027
|
+
expand_all: bool = RICH_EXPAND_ALL,
|
1028
|
+
) -> Iterator[str]:
|
1029
|
+
module = _path_to_dots(frame.filename)
|
1030
|
+
yield f"{module}:{frame.lineno} | {frame.name} | {frame.line}"
|
1031
|
+
if frame.locals is not None:
|
1032
|
+
yield from yield_mapping_repr(
|
1033
|
+
frame.locals,
|
1034
|
+
_max_width=max_width,
|
1035
|
+
_indent_size=indent_size,
|
1036
|
+
_max_length=max_length,
|
1037
|
+
_max_string=max_string,
|
1038
|
+
_max_depth=max_depth,
|
1039
|
+
_expand_all=expand_all,
|
1040
|
+
)
|
1041
|
+
|
1042
|
+
|
1043
|
+
def _path_to_dots(path: PathLike, /) -> str:
|
1044
|
+
new_path: Path | None = None
|
1045
|
+
for pattern in [
|
1046
|
+
"site-packages",
|
1047
|
+
".venv", # after site-packages
|
1048
|
+
"src",
|
1049
|
+
r"python\d+\.\d+",
|
1050
|
+
]:
|
1051
|
+
if (new_path := _trim_path(path, pattern)) is not None:
|
1052
|
+
break
|
1053
|
+
path_use = Path(path) if new_path is None else new_path
|
1054
|
+
return ".".join(path_use.with_suffix("").parts)
|
1055
|
+
|
1056
|
+
|
1057
|
+
def _trim_path(path: PathLike, pattern: str, /) -> Path | None:
|
1058
|
+
parts = Path(path).parts
|
1059
|
+
compiled = re.compile(f"^{pattern}$")
|
1060
|
+
try:
|
1061
|
+
i = one(i for i, p in enumerate(parts) if compiled.search(p))
|
1062
|
+
except OneEmptyError:
|
1063
|
+
return None
|
1064
|
+
return Path(*parts[i + 1 :])
|
1065
|
+
|
1066
|
+
|
1067
|
+
@dataclass(kw_only=True, slots=True)
|
1068
|
+
class MakeExceptHookError(Exception):
|
1069
|
+
@override
|
1070
|
+
def __str__(self) -> str:
|
1071
|
+
return "No exception to log"
|
1072
|
+
|
1073
|
+
|
810
1074
|
__all__ = [
|
811
1075
|
"ExcChainTB",
|
812
1076
|
"ExcGroupTB",
|
813
1077
|
"ExcTB",
|
814
1078
|
"RichTracebackFormatter",
|
1079
|
+
"format_exception_stack",
|
815
1080
|
"get_rich_traceback",
|
1081
|
+
"make_except_hook",
|
816
1082
|
"trace",
|
817
1083
|
"yield_exceptions",
|
818
1084
|
"yield_extended_frame_summaries",
|
utilities/types.py
CHANGED
@@ -241,8 +241,8 @@ type SerializeObjectExtra = Mapping[Any, Callable[[Any], str]]
|
|
241
241
|
|
242
242
|
|
243
243
|
# pathlib
|
244
|
+
type MaybeCallablePathLike = MaybeCallable[PathLike]
|
244
245
|
type PathLike = MaybeStr[Path]
|
245
|
-
type PathLikeOrCallable = PathLike | Callable[[], PathLike]
|
246
246
|
|
247
247
|
|
248
248
|
# random
|
@@ -282,6 +282,7 @@ __all__ = [
|
|
282
282
|
"MaybeCallableDate",
|
283
283
|
"MaybeCallableDateTime",
|
284
284
|
"MaybeCallableEvent",
|
285
|
+
"MaybeCallablePathLike",
|
285
286
|
"MaybeCoroutine1",
|
286
287
|
"MaybeIterable",
|
287
288
|
"MaybeIterableHashable",
|
@@ -293,7 +294,6 @@ __all__ = [
|
|
293
294
|
"Parallelism",
|
294
295
|
"ParseObjectExtra",
|
295
296
|
"PathLike",
|
296
|
-
"PathLikeOrCallable",
|
297
297
|
"RoundMode",
|
298
298
|
"Seed",
|
299
299
|
"SerializeObjectExtra",
|
utilities/version.py
CHANGED
@@ -137,14 +137,6 @@ def get_version(*, version: MaybeCallableVersionLike) -> Version: ...
|
|
137
137
|
def get_version(*, version: None) -> None: ...
|
138
138
|
@overload
|
139
139
|
def get_version(*, version: Sentinel) -> Sentinel: ...
|
140
|
-
@overload
|
141
|
-
def get_version(
|
142
|
-
*, version: MaybeCallableVersionLike | Sentinel
|
143
|
-
) -> Version | Sentinel: ...
|
144
|
-
@overload
|
145
|
-
def get_version(
|
146
|
-
*, version: MaybeCallableVersionLike | None | Sentinel = sentinel
|
147
|
-
) -> Version | None | Sentinel: ...
|
148
140
|
def get_version(
|
149
141
|
*, version: MaybeCallableVersionLike | None | Sentinel = sentinel
|
150
142
|
) -> Version | None | Sentinel:
|
utilities/whenever.py
CHANGED
@@ -4,7 +4,9 @@ import datetime as dt
|
|
4
4
|
import re
|
5
5
|
from contextlib import suppress
|
6
6
|
from dataclasses import dataclass
|
7
|
-
from
|
7
|
+
from functools import cache
|
8
|
+
from logging import LogRecord
|
9
|
+
from typing import TYPE_CHECKING, Any, override
|
8
10
|
|
9
11
|
from whenever import (
|
10
12
|
Date,
|
@@ -33,6 +35,8 @@ from utilities.re import (
|
|
33
35
|
from utilities.zoneinfo import UTC, ensure_time_zone, get_time_zone_name
|
34
36
|
|
35
37
|
if TYPE_CHECKING:
|
38
|
+
from zoneinfo import ZoneInfo
|
39
|
+
|
36
40
|
from utilities.types import (
|
37
41
|
DateLike,
|
38
42
|
DateTimeLike,
|
@@ -561,6 +565,64 @@ class SerializeZonedDateTimeError(Exception):
|
|
561
565
|
##
|
562
566
|
|
563
567
|
|
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
|
+
|
564
626
|
def _to_datetime_delta(timedelta: dt.timedelta, /) -> DateTimeDelta:
|
565
627
|
"""Serialize a timedelta."""
|
566
628
|
total_microseconds = datetime_duration_to_microseconds(timedelta)
|
@@ -610,6 +672,7 @@ __all__ = [
|
|
610
672
|
"SerializePlainDateTimeError",
|
611
673
|
"SerializeTimeDeltaError",
|
612
674
|
"SerializeZonedDateTimeError",
|
675
|
+
"WheneverLogRecord",
|
613
676
|
"check_valid_zoned_datetime",
|
614
677
|
"ensure_date",
|
615
678
|
"ensure_datetime",
|
File without changes
|
File without changes
|