dycw-utilities 0.129.8__py3-none-any.whl → 0.129.9__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.129.8.dist-info → dycw_utilities-0.129.9.dist-info}/METADATA +1 -1
- {dycw_utilities-0.129.8.dist-info → dycw_utilities-0.129.9.dist-info}/RECORD +6 -6
- utilities/__init__.py +1 -1
- utilities/traceback.py +241 -6
- {dycw_utilities-0.129.8.dist-info → dycw_utilities-0.129.9.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.129.8.dist-info → dycw_utilities-0.129.9.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
utilities/__init__.py,sha256=
|
1
|
+
utilities/__init__.py,sha256=uFYFepkZNArEi_sHiw7tlOn88WgLGjxnMihyD4GzfOQ,60
|
2
2
|
utilities/altair.py,sha256=Gpja-flOo-Db0PIPJLJsgzAlXWoKUjPU1qY-DQ829ek,9156
|
3
3
|
utilities/asyncio.py,sha256=3n5EIcSq2xtEF1i4oR0oY2JmBq3NyugeHKFK39Mt22s,37987
|
4
4
|
utilities/atomicwrites.py,sha256=geFjn9Pwn-tTrtoGjDDxWli9NqbYfy3gGL6ZBctiqSo,5393
|
@@ -78,7 +78,7 @@ utilities/tenacity.py,sha256=1PUvODiBVgeqIh7G5TRt5WWMSqjLYkEqP53itT97WQc,4914
|
|
78
78
|
utilities/text.py,sha256=ymBFlP_cA8OgNnZRVNs7FAh7OG8HxE6YkiLEMZv5g_A,11297
|
79
79
|
utilities/threading.py,sha256=GvBOp4CyhHfN90wGXZuA2VKe9fGzMaEa7oCl4f3nnPU,1009
|
80
80
|
utilities/timer.py,sha256=Rkc49KSpHuC8s7vUxGO9DU55U9I6yDKnchsQqrUCVBs,4075
|
81
|
-
utilities/traceback.py,sha256=
|
81
|
+
utilities/traceback.py,sha256=EUm-5jhH1RICBmsG_H77-CXvD-IVErfkzGRSqnvR0dI,35782
|
82
82
|
utilities/types.py,sha256=gP04CcCOyFrG7BgblVCsrrChiuO2x842NDVW-GF7odo,18370
|
83
83
|
utilities/typing.py,sha256=H6ysJkI830aRwLsMKz0SZIw4cpcsm7d6KhQOwr-SDh0,13817
|
84
84
|
utilities/tzdata.py,sha256=yCf70NICwAeazN3_JcXhWvRqCy06XJNQ42j7r6gw3HY,1217
|
@@ -89,7 +89,7 @@ utilities/warnings.py,sha256=un1LvHv70PU-LLv8RxPVmugTzDJkkGXRMZTE2-fTQHw,1771
|
|
89
89
|
utilities/whenever.py,sha256=QbXgFAPuUL7PCp2hajmIP-FFIfIR1J6Y0TxJbeoj60I,18434
|
90
90
|
utilities/zipfile.py,sha256=24lQc9ATcJxHXBPc_tBDiJk48pWyRrlxO2fIsFxU0A8,699
|
91
91
|
utilities/zoneinfo.py,sha256=-5j7IQ9nb7gR43rdgA7ms05im-XuqhAk9EJnQBXxCoQ,1874
|
92
|
-
dycw_utilities-0.129.
|
93
|
-
dycw_utilities-0.129.
|
94
|
-
dycw_utilities-0.129.
|
95
|
-
dycw_utilities-0.129.
|
92
|
+
dycw_utilities-0.129.9.dist-info/METADATA,sha256=c0KdCG0ORHKUnqbJ4eaGnDoZNUDbRtkjupd_8Oqg22c,12723
|
93
|
+
dycw_utilities-0.129.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
94
|
+
dycw_utilities-0.129.9.dist-info/licenses/LICENSE,sha256=gppZp16M6nSVpBbUBrNL6JuYfvKwZiKgV7XoKKsHzqo,1066
|
95
|
+
dycw_utilities-0.129.9.dist-info/RECORD,,
|
utilities/__init__.py
CHANGED
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,8 +30,8 @@ from typing import (
|
|
26
30
|
runtime_checkable,
|
27
31
|
)
|
28
32
|
|
29
|
-
from utilities.datetime import get_datetime, get_now
|
30
|
-
from utilities.errors import ImpossibleCaseError
|
33
|
+
from utilities.datetime import get_datetime, get_now, serialize_compact
|
34
|
+
from utilities.errors import ImpossibleCaseError, repr_error
|
31
35
|
from utilities.functions import (
|
32
36
|
ensure_not_none,
|
33
37
|
ensure_str,
|
@@ -35,7 +39,8 @@ from utilities.functions import (
|
|
35
39
|
get_func_name,
|
36
40
|
get_func_qualname,
|
37
41
|
)
|
38
|
-
from utilities.iterables import always_iterable, one
|
42
|
+
from utilities.iterables import OneEmptyError, always_iterable, one
|
43
|
+
from utilities.pathlib import get_path
|
39
44
|
from utilities.reprlib import (
|
40
45
|
RICH_EXPAND_ALL,
|
41
46
|
RICH_INDENT_SIZE,
|
@@ -46,12 +51,18 @@ from utilities.reprlib import (
|
|
46
51
|
yield_call_args_repr,
|
47
52
|
yield_mapping_repr,
|
48
53
|
)
|
49
|
-
from utilities.types import
|
54
|
+
from utilities.types import (
|
55
|
+
MaybeCallableDateTime,
|
56
|
+
MaybeCallablePathLike,
|
57
|
+
PathLike,
|
58
|
+
TBaseException,
|
59
|
+
TCallable,
|
60
|
+
)
|
50
61
|
from utilities.version import get_version
|
51
62
|
from utilities.whenever import serialize_duration
|
52
63
|
|
53
64
|
if TYPE_CHECKING:
|
54
|
-
from collections.abc import Callable, Iterable, Iterator
|
65
|
+
from collections.abc import Callable, Iterable, Iterator, Sequence
|
55
66
|
from logging import _FormatStyle
|
56
67
|
from types import FrameType, TracebackType
|
57
68
|
|
@@ -68,6 +79,131 @@ _START = get_now()
|
|
68
79
|
##
|
69
80
|
|
70
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))
|
202
|
+
|
203
|
+
|
204
|
+
##
|
205
|
+
|
206
|
+
|
71
207
|
class RichTracebackFormatter(Formatter):
|
72
208
|
"""Formatter for rich tracebacks."""
|
73
209
|
|
@@ -811,6 +947,9 @@ def _merge_frames(
|
|
811
947
|
return values[::-1]
|
812
948
|
|
813
949
|
|
950
|
+
##
|
951
|
+
|
952
|
+
|
814
953
|
def _yield_header_lines(
|
815
954
|
*,
|
816
955
|
start: MaybeCallableDateTime | None = _START,
|
@@ -838,12 +977,108 @@ def _yield_header_lines(
|
|
838
977
|
yield ""
|
839
978
|
|
840
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
|
+
|
841
1074
|
__all__ = [
|
842
1075
|
"ExcChainTB",
|
843
1076
|
"ExcGroupTB",
|
844
1077
|
"ExcTB",
|
845
1078
|
"RichTracebackFormatter",
|
1079
|
+
"format_exception_stack",
|
846
1080
|
"get_rich_traceback",
|
1081
|
+
"make_except_hook",
|
847
1082
|
"trace",
|
848
1083
|
"yield_exceptions",
|
849
1084
|
"yield_extended_frame_summaries",
|
File without changes
|
File without changes
|