dycw-utilities 0.129.14__py3-none-any.whl → 0.130.0__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.14.dist-info → dycw_utilities-0.130.0.dist-info}/METADATA +26 -28
- {dycw_utilities-0.129.14.dist-info → dycw_utilities-0.130.0.dist-info}/RECORD +11 -13
- utilities/__init__.py +1 -1
- utilities/asyncio.py +9 -4
- utilities/logging.py +276 -498
- utilities/redis.py +3 -3
- utilities/sqlalchemy.py +8 -2
- utilities/sqlalchemy_polars.py +2 -2
- utilities/traceback.py +102 -901
- utilities/loguru.py +0 -144
- utilities/sys.py +0 -87
- {dycw_utilities-0.129.14.dist-info → dycw_utilities-0.130.0.dist-info}/WHEEL +0 -0
- {dycw_utilities-0.129.14.dist-info → dycw_utilities-0.130.0.dist-info}/licenses/LICENSE +0 -0
utilities/traceback.py
CHANGED
@@ -3,43 +3,19 @@ from __future__ import annotations
|
|
3
3
|
import re
|
4
4
|
import sys
|
5
5
|
from asyncio import run
|
6
|
-
from collections.abc import Callable
|
7
|
-
from dataclasses import dataclass
|
8
|
-
from functools import partial
|
6
|
+
from collections.abc import Callable
|
7
|
+
from dataclasses import dataclass
|
8
|
+
from functools import partial
|
9
9
|
from getpass import getuser
|
10
|
-
from inspect import iscoroutinefunction, signature
|
11
10
|
from itertools import repeat
|
12
|
-
from logging import Formatter, Handler, LogRecord
|
13
11
|
from pathlib import Path
|
14
12
|
from socket import gethostname
|
15
|
-
from
|
16
|
-
from
|
17
|
-
from traceback import FrameSummary, TracebackException, format_exception
|
18
|
-
from typing import (
|
19
|
-
TYPE_CHECKING,
|
20
|
-
Any,
|
21
|
-
Generic,
|
22
|
-
Protocol,
|
23
|
-
Self,
|
24
|
-
TypeGuard,
|
25
|
-
TypeVar,
|
26
|
-
assert_never,
|
27
|
-
cast,
|
28
|
-
overload,
|
29
|
-
override,
|
30
|
-
runtime_checkable,
|
31
|
-
)
|
13
|
+
from traceback import TracebackException
|
14
|
+
from typing import TYPE_CHECKING, override
|
32
15
|
|
33
16
|
from utilities.datetime import get_datetime, get_now, serialize_compact
|
34
|
-
from utilities.errors import
|
35
|
-
from utilities.
|
36
|
-
ensure_not_none,
|
37
|
-
ensure_str,
|
38
|
-
get_class_name,
|
39
|
-
get_func_name,
|
40
|
-
get_func_qualname,
|
41
|
-
)
|
42
|
-
from utilities.iterables import OneEmptyError, always_iterable, one
|
17
|
+
from utilities.errors import repr_error
|
18
|
+
from utilities.iterables import OneEmptyError, one
|
43
19
|
from utilities.pathlib import get_path
|
44
20
|
from utilities.reprlib import (
|
45
21
|
RICH_EXPAND_ALL,
|
@@ -48,35 +24,24 @@ from utilities.reprlib import (
|
|
48
24
|
RICH_MAX_LENGTH,
|
49
25
|
RICH_MAX_STRING,
|
50
26
|
RICH_MAX_WIDTH,
|
51
|
-
yield_call_args_repr,
|
52
27
|
yield_mapping_repr,
|
53
28
|
)
|
54
|
-
from utilities.types import (
|
55
|
-
MaybeCallableDateTime,
|
56
|
-
MaybeCallablePathLike,
|
57
|
-
PathLike,
|
58
|
-
TBaseException,
|
59
|
-
TCallable,
|
60
|
-
)
|
61
29
|
from utilities.version import get_version
|
62
30
|
from utilities.whenever import serialize_duration
|
63
31
|
|
64
32
|
if TYPE_CHECKING:
|
65
|
-
from collections.abc import Callable,
|
66
|
-
from
|
67
|
-
from types import
|
33
|
+
from collections.abc import Callable, Iterator, Sequence
|
34
|
+
from traceback import FrameSummary
|
35
|
+
from types import TracebackType
|
68
36
|
|
69
|
-
from utilities.types import
|
37
|
+
from utilities.types import MaybeCallableDateTime, MaybeCallablePathLike, PathLike
|
70
38
|
from utilities.version import MaybeCallableVersionLike
|
71
39
|
|
72
40
|
|
73
|
-
|
74
|
-
_CALL_ARGS = "_CALL_ARGS"
|
75
|
-
_INDENT = 4 * " "
|
76
|
-
_START = get_now()
|
41
|
+
##
|
77
42
|
|
78
43
|
|
79
|
-
|
44
|
+
_START = get_now()
|
80
45
|
|
81
46
|
|
82
47
|
def format_exception_stack(
|
@@ -113,843 +78,6 @@ def format_exception_stack(
|
|
113
78
|
return "\n".join(lines)
|
114
79
|
|
115
80
|
|
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
|
-
|
207
|
-
class RichTracebackFormatter(Formatter):
|
208
|
-
"""Formatter for rich tracebacks."""
|
209
|
-
|
210
|
-
@override
|
211
|
-
def __init__(
|
212
|
-
self,
|
213
|
-
fmt: str | None = None,
|
214
|
-
datefmt: str | None = None,
|
215
|
-
style: _FormatStyle = "%",
|
216
|
-
validate: bool = True,
|
217
|
-
/,
|
218
|
-
*,
|
219
|
-
defaults: StrMapping | None = None,
|
220
|
-
start: MaybeCallableDateTime | None = _START,
|
221
|
-
version: MaybeCallableVersionLike | None = None,
|
222
|
-
max_width: int = RICH_MAX_WIDTH,
|
223
|
-
indent_size: int = RICH_INDENT_SIZE,
|
224
|
-
max_length: int | None = RICH_MAX_LENGTH,
|
225
|
-
max_string: int | None = RICH_MAX_STRING,
|
226
|
-
max_depth: int | None = RICH_MAX_DEPTH,
|
227
|
-
expand_all: bool = RICH_EXPAND_ALL,
|
228
|
-
detail: bool = False,
|
229
|
-
post: Callable[[str], str] | None = None,
|
230
|
-
) -> None:
|
231
|
-
super().__init__(fmt, datefmt, style, validate, defaults=defaults)
|
232
|
-
self._start = get_datetime(datetime=start)
|
233
|
-
self._version = get_version(version=version)
|
234
|
-
self._max_width = max_width
|
235
|
-
self._indent_size = indent_size
|
236
|
-
self._max_length = max_length
|
237
|
-
self._max_string = max_string
|
238
|
-
self._max_depth = max_depth
|
239
|
-
self._expand_all = expand_all
|
240
|
-
self._detail = detail
|
241
|
-
self._post = post
|
242
|
-
|
243
|
-
@override
|
244
|
-
def format(self, record: LogRecord) -> str:
|
245
|
-
"""Format the record."""
|
246
|
-
if record.exc_info is None:
|
247
|
-
return f"ERROR: record.exc_info is None\n{record=}"
|
248
|
-
_, exc_value, _ = record.exc_info
|
249
|
-
if exc_value is None:
|
250
|
-
return f"ERROR: record.exc_info[1] is None\n{record=}" # pragma: no cover
|
251
|
-
exc_value = ensure_not_none(exc_value, desc="exc_value")
|
252
|
-
error = get_rich_traceback(
|
253
|
-
exc_value,
|
254
|
-
start=self._start,
|
255
|
-
version=self._version,
|
256
|
-
max_width=self._max_width,
|
257
|
-
indent_size=self._indent_size,
|
258
|
-
max_length=self._max_length,
|
259
|
-
max_string=self._max_string,
|
260
|
-
max_depth=self._max_depth,
|
261
|
-
expand_all=self._expand_all,
|
262
|
-
)
|
263
|
-
match error:
|
264
|
-
case ExcChainTB() | ExcGroupTB() | ExcTB():
|
265
|
-
text = error.format(header=True, detail=self._detail)
|
266
|
-
case BaseException():
|
267
|
-
text = "\n".join(format_exception(error))
|
268
|
-
case _ as never:
|
269
|
-
assert_never(never)
|
270
|
-
if self._post is not None:
|
271
|
-
text = self._post(text)
|
272
|
-
return text
|
273
|
-
|
274
|
-
@classmethod
|
275
|
-
def create_and_set(
|
276
|
-
cls,
|
277
|
-
handler: Handler,
|
278
|
-
/,
|
279
|
-
*,
|
280
|
-
fmt: str | None = None,
|
281
|
-
datefmt: str | None = None,
|
282
|
-
style: _FormatStyle = "%",
|
283
|
-
validate: bool = True,
|
284
|
-
defaults: StrMapping | None = None,
|
285
|
-
version: MaybeCallableVersionLike | None = None,
|
286
|
-
max_width: int = RICH_MAX_WIDTH,
|
287
|
-
indent_size: int = RICH_INDENT_SIZE,
|
288
|
-
max_length: int | None = RICH_MAX_LENGTH,
|
289
|
-
max_string: int | None = RICH_MAX_STRING,
|
290
|
-
max_depth: int | None = RICH_MAX_DEPTH,
|
291
|
-
expand_all: bool = RICH_EXPAND_ALL,
|
292
|
-
detail: bool = False,
|
293
|
-
post: Callable[[str], str] | None = None,
|
294
|
-
) -> Self:
|
295
|
-
"""Create an instance and set it on a handler."""
|
296
|
-
formatter = cls(
|
297
|
-
fmt,
|
298
|
-
datefmt,
|
299
|
-
style,
|
300
|
-
validate,
|
301
|
-
defaults=defaults,
|
302
|
-
version=version,
|
303
|
-
max_width=max_width,
|
304
|
-
indent_size=indent_size,
|
305
|
-
max_length=max_length,
|
306
|
-
max_string=max_string,
|
307
|
-
max_depth=max_depth,
|
308
|
-
expand_all=expand_all,
|
309
|
-
detail=detail,
|
310
|
-
post=post,
|
311
|
-
)
|
312
|
-
handler.addFilter(cls._has_exc_info)
|
313
|
-
handler.setFormatter(formatter)
|
314
|
-
return formatter
|
315
|
-
|
316
|
-
@classmethod
|
317
|
-
def _has_exc_info(cls, record: LogRecord, /) -> bool:
|
318
|
-
return record.exc_info is not None
|
319
|
-
|
320
|
-
|
321
|
-
##
|
322
|
-
|
323
|
-
|
324
|
-
@dataclass(repr=False, kw_only=True, slots=True)
|
325
|
-
class _CallArgs:
|
326
|
-
"""A collection of call arguments."""
|
327
|
-
|
328
|
-
func: Callable[..., Any]
|
329
|
-
args: tuple[Any, ...] = field(default_factory=tuple)
|
330
|
-
kwargs: dict[str, Any] = field(default_factory=dict)
|
331
|
-
|
332
|
-
@override
|
333
|
-
def __repr__(self) -> str:
|
334
|
-
cls = get_class_name(self)
|
335
|
-
parts: list[tuple[str, Any]] = [
|
336
|
-
("func", get_func_qualname(self.func)),
|
337
|
-
("args", self.args),
|
338
|
-
("kwargs", self.kwargs),
|
339
|
-
]
|
340
|
-
joined = ", ".join(f"{k}={v!r}" for k, v in parts)
|
341
|
-
return f"{cls}({joined})"
|
342
|
-
|
343
|
-
@classmethod
|
344
|
-
def create(cls, func: Callable[..., Any], *args: Any, **kwargs: Any) -> Self:
|
345
|
-
"""Make the initial trace data."""
|
346
|
-
sig = signature(func)
|
347
|
-
try:
|
348
|
-
bound_args = sig.bind(*args, **kwargs)
|
349
|
-
except TypeError as error:
|
350
|
-
orig = ensure_str(one(error.args))
|
351
|
-
lines: list[str] = [
|
352
|
-
f"Unable to bind arguments for {get_func_name(func)!r}; {orig}"
|
353
|
-
]
|
354
|
-
lines.extend(yield_call_args_repr(*args, **kwargs))
|
355
|
-
new = "\n".join(lines)
|
356
|
-
raise _CallArgsError(new) from None
|
357
|
-
return cls(func=func, args=bound_args.args, kwargs=bound_args.kwargs)
|
358
|
-
|
359
|
-
|
360
|
-
class _CallArgsError(TypeError):
|
361
|
-
"""Raised when a set of call arguments cannot be created."""
|
362
|
-
|
363
|
-
|
364
|
-
@dataclass(kw_only=True, slots=True)
|
365
|
-
class _ExtFrameSummary(Generic[_T]):
|
366
|
-
"""An extended frame summary."""
|
367
|
-
|
368
|
-
filename: Path
|
369
|
-
module: str | None = None
|
370
|
-
name: str
|
371
|
-
qualname: str
|
372
|
-
code_line: str
|
373
|
-
first_line_num: int
|
374
|
-
line_num: int
|
375
|
-
end_line_num: int
|
376
|
-
col_num: int | None = None
|
377
|
-
end_col_num: int | None = None
|
378
|
-
locals: dict[str, Any] = field(default_factory=dict)
|
379
|
-
extra: _T
|
380
|
-
|
381
|
-
|
382
|
-
type _ExtFrameSummaryCAOpt = _ExtFrameSummary[_CallArgs | None]
|
383
|
-
type _ExtFrameSummaryCA = _ExtFrameSummary[_CallArgs]
|
384
|
-
|
385
|
-
|
386
|
-
@dataclass(repr=False, kw_only=True, slots=True)
|
387
|
-
class _ExcTBInternal:
|
388
|
-
"""A rich traceback for an exception; internal use only."""
|
389
|
-
|
390
|
-
raw: list[_ExtFrameSummaryCAOpt] = field(default_factory=list)
|
391
|
-
frames: list[_ExtFrameSummaryCA] = field(default_factory=list)
|
392
|
-
error: BaseException
|
393
|
-
|
394
|
-
|
395
|
-
@runtime_checkable
|
396
|
-
class _HasExceptionPath(Protocol):
|
397
|
-
@property
|
398
|
-
def exc_tb(self) -> _ExcTBInternal: ... # pragma: no cover
|
399
|
-
|
400
|
-
|
401
|
-
@dataclass(kw_only=True, slots=True)
|
402
|
-
class ExcChainTB(Generic[TBaseException]):
|
403
|
-
"""A rich traceback for an exception chain."""
|
404
|
-
|
405
|
-
errors: list[
|
406
|
-
ExcGroupTB[TBaseException] | ExcTB[TBaseException] | TBaseException
|
407
|
-
] = field(default_factory=list)
|
408
|
-
start: MaybeCallableDateTime | None = field(default=_START, repr=False)
|
409
|
-
version: MaybeCallableVersionLike | None = field(default=None, repr=False)
|
410
|
-
max_width: int = RICH_MAX_WIDTH
|
411
|
-
indent_size: int = RICH_INDENT_SIZE
|
412
|
-
max_length: int | None = RICH_MAX_LENGTH
|
413
|
-
max_string: int | None = RICH_MAX_STRING
|
414
|
-
max_depth: int | None = RICH_MAX_DEPTH
|
415
|
-
expand_all: bool = RICH_EXPAND_ALL
|
416
|
-
|
417
|
-
def __getitem__(
|
418
|
-
self, i: int, /
|
419
|
-
) -> ExcGroupTB[TBaseException] | ExcTB[TBaseException] | TBaseException:
|
420
|
-
return self.errors[i]
|
421
|
-
|
422
|
-
def __iter__(
|
423
|
-
self,
|
424
|
-
) -> Iterator[ExcGroupTB[TBaseException] | ExcTB[TBaseException] | TBaseException]:
|
425
|
-
yield from self.errors
|
426
|
-
|
427
|
-
def __len__(self) -> int:
|
428
|
-
return len(self.errors)
|
429
|
-
|
430
|
-
@override
|
431
|
-
def __repr__(self) -> str:
|
432
|
-
return self.format(header=True, detail=True)
|
433
|
-
|
434
|
-
def format(self, *, header: bool = False, detail: bool = False) -> str:
|
435
|
-
"""Format the traceback."""
|
436
|
-
lines: list[str] = []
|
437
|
-
if header: # pragma: no cover
|
438
|
-
lines.extend(_yield_header_lines(start=self.start, version=self.version))
|
439
|
-
total = len(self.errors)
|
440
|
-
for i, errors in enumerate(self.errors, start=1):
|
441
|
-
lines.append(f"Exception chain {i}/{total}:")
|
442
|
-
match errors:
|
443
|
-
case ExcGroupTB() | ExcTB():
|
444
|
-
lines.append(errors.format(header=False, detail=detail, depth=1))
|
445
|
-
case BaseException(): # pragma: no cover
|
446
|
-
lines.append(_format_exception(errors, depth=1))
|
447
|
-
case _ as never:
|
448
|
-
assert_never(never)
|
449
|
-
lines.append("")
|
450
|
-
return "\n".join(lines)
|
451
|
-
|
452
|
-
|
453
|
-
@dataclass(kw_only=True, slots=True)
|
454
|
-
class ExcGroupTB(Generic[TBaseException]):
|
455
|
-
"""A rich traceback for an exception group."""
|
456
|
-
|
457
|
-
exc_group: ExcTB[ExceptionGroup[Any]] | ExceptionGroup[Any]
|
458
|
-
errors: list[
|
459
|
-
ExcGroupTB[TBaseException] | ExcTB[TBaseException] | TBaseException
|
460
|
-
] = field(default_factory=list)
|
461
|
-
start: MaybeCallableDateTime | None = field(default=_START, repr=False)
|
462
|
-
version: MaybeCallableVersionLike | None = field(default=None, repr=False)
|
463
|
-
max_width: int = RICH_MAX_WIDTH
|
464
|
-
indent_size: int = RICH_INDENT_SIZE
|
465
|
-
max_length: int | None = RICH_MAX_LENGTH
|
466
|
-
max_string: int | None = RICH_MAX_STRING
|
467
|
-
max_depth: int | None = RICH_MAX_DEPTH
|
468
|
-
expand_all: bool = RICH_EXPAND_ALL
|
469
|
-
|
470
|
-
@override
|
471
|
-
def __repr__(self) -> str:
|
472
|
-
return self.format(header=True, detail=True) # skipif-ci
|
473
|
-
|
474
|
-
def format(
|
475
|
-
self, *, header: bool = False, detail: bool = False, depth: int = 0
|
476
|
-
) -> str:
|
477
|
-
"""Format the traceback."""
|
478
|
-
lines: list[str] = [] # skipif-ci
|
479
|
-
if header: # pragma: no cover
|
480
|
-
lines.extend(_yield_header_lines(start=self.start, version=self.version))
|
481
|
-
lines.append("Exception group:") # skipif-ci
|
482
|
-
match self.exc_group: # skipif-ci
|
483
|
-
case ExcTB() as exc_tb:
|
484
|
-
lines.append(exc_tb.format(header=False, detail=detail, depth=1))
|
485
|
-
case ExceptionGroup() as exc_group: # pragma: no cover
|
486
|
-
lines.append(_format_exception(exc_group, depth=1))
|
487
|
-
case _ as never:
|
488
|
-
assert_never(never)
|
489
|
-
lines.append("") # skipif-ci
|
490
|
-
total = len(self.errors) # skipif-ci
|
491
|
-
for i, errors in enumerate(self.errors, start=1): # skipif-ci
|
492
|
-
lines.append(indent(f"Exception group error {i}/{total}:", _INDENT))
|
493
|
-
match errors:
|
494
|
-
case ExcGroupTB() | ExcTB(): # pragma: no cover
|
495
|
-
lines.append(errors.format(header=False, detail=detail, depth=2))
|
496
|
-
case BaseException(): # pragma: no cover
|
497
|
-
lines.append(_format_exception(errors, depth=2))
|
498
|
-
case _ as never:
|
499
|
-
assert_never(never)
|
500
|
-
lines.append("")
|
501
|
-
return indent("\n".join(lines), depth * _INDENT) # skipif-ci
|
502
|
-
|
503
|
-
|
504
|
-
@dataclass(kw_only=True, slots=True)
|
505
|
-
class ExcTB(Generic[TBaseException]):
|
506
|
-
"""A rich traceback for a single exception."""
|
507
|
-
|
508
|
-
frames: list[_Frame] = field(default_factory=list)
|
509
|
-
error: TBaseException
|
510
|
-
start: MaybeCallableDateTime | None = field(default=_START, repr=False)
|
511
|
-
version: MaybeCallableVersionLike | None = field(default=None, repr=False)
|
512
|
-
max_width: int = RICH_MAX_WIDTH
|
513
|
-
indent_size: int = RICH_INDENT_SIZE
|
514
|
-
max_length: int | None = RICH_MAX_LENGTH
|
515
|
-
max_string: int | None = RICH_MAX_STRING
|
516
|
-
max_depth: int | None = RICH_MAX_DEPTH
|
517
|
-
expand_all: bool = RICH_EXPAND_ALL
|
518
|
-
|
519
|
-
def __getitem__(self, i: int, /) -> _Frame:
|
520
|
-
return self.frames[i]
|
521
|
-
|
522
|
-
def __iter__(self) -> Iterator[_Frame]:
|
523
|
-
yield from self.frames
|
524
|
-
|
525
|
-
def __len__(self) -> int:
|
526
|
-
return len(self.frames)
|
527
|
-
|
528
|
-
@override
|
529
|
-
def __repr__(self) -> str:
|
530
|
-
return self.format(header=True, detail=True)
|
531
|
-
|
532
|
-
def format(
|
533
|
-
self, *, header: bool = False, detail: bool = False, depth: int = 0
|
534
|
-
) -> str:
|
535
|
-
"""Format the traceback."""
|
536
|
-
total = len(self)
|
537
|
-
lines: list[str] = []
|
538
|
-
if header: # pragma: no cover
|
539
|
-
lines.extend(_yield_header_lines(start=self.start, version=self.version))
|
540
|
-
for i, frame in enumerate(self.frames):
|
541
|
-
is_head = i < total - 1
|
542
|
-
lines.append(
|
543
|
-
frame.format(
|
544
|
-
index=i,
|
545
|
-
total=total,
|
546
|
-
detail=detail,
|
547
|
-
error=None if is_head else self.error,
|
548
|
-
)
|
549
|
-
)
|
550
|
-
if detail and is_head:
|
551
|
-
lines.append("")
|
552
|
-
return indent("\n".join(lines), depth * _INDENT)
|
553
|
-
|
554
|
-
|
555
|
-
@dataclass(kw_only=True, slots=True)
|
556
|
-
class _Frame:
|
557
|
-
module: str | None = None
|
558
|
-
name: str
|
559
|
-
code_line: str
|
560
|
-
line_num: int
|
561
|
-
args: tuple[Any, ...] = field(default_factory=tuple)
|
562
|
-
kwargs: dict[str, Any] = field(default_factory=dict)
|
563
|
-
locals: dict[str, Any] = field(default_factory=dict)
|
564
|
-
max_width: int = RICH_MAX_WIDTH
|
565
|
-
indent_size: int = RICH_INDENT_SIZE
|
566
|
-
max_length: int | None = RICH_MAX_LENGTH
|
567
|
-
max_string: int | None = RICH_MAX_STRING
|
568
|
-
max_depth: int | None = RICH_MAX_DEPTH
|
569
|
-
expand_all: bool = RICH_EXPAND_ALL
|
570
|
-
|
571
|
-
@override
|
572
|
-
def __repr__(self) -> str:
|
573
|
-
return self.format(detail=True)
|
574
|
-
|
575
|
-
def format(
|
576
|
-
self,
|
577
|
-
*,
|
578
|
-
index: int = 0,
|
579
|
-
total: int = 1,
|
580
|
-
detail: bool = False,
|
581
|
-
error: BaseException | None = None,
|
582
|
-
depth: int = 0,
|
583
|
-
) -> str:
|
584
|
-
"""Format the traceback."""
|
585
|
-
lines: list[str] = [f"Frame {index + 1}/{total}: {self.name} ({self.module})"]
|
586
|
-
if detail:
|
587
|
-
lines.append(indent("Inputs:", _INDENT))
|
588
|
-
lines.extend(
|
589
|
-
indent(line, 2 * _INDENT)
|
590
|
-
for line in yield_call_args_repr(
|
591
|
-
*self.args,
|
592
|
-
_max_width=self.max_width,
|
593
|
-
_indent_size=self.indent_size,
|
594
|
-
_max_length=self.max_length,
|
595
|
-
_max_string=self.max_string,
|
596
|
-
_max_depth=self.max_depth,
|
597
|
-
_expand_all=self.expand_all,
|
598
|
-
**self.kwargs,
|
599
|
-
)
|
600
|
-
)
|
601
|
-
lines.append(indent("Locals:", _INDENT))
|
602
|
-
lines.extend(
|
603
|
-
indent(line, 2 * _INDENT)
|
604
|
-
for line in yield_mapping_repr(
|
605
|
-
self.locals,
|
606
|
-
_max_width=self.max_width,
|
607
|
-
_indent_size=self.indent_size,
|
608
|
-
_max_length=self.max_length,
|
609
|
-
_max_string=self.max_string,
|
610
|
-
_max_depth=self.max_depth,
|
611
|
-
_expand_all=self.expand_all,
|
612
|
-
)
|
613
|
-
)
|
614
|
-
lines.extend([
|
615
|
-
indent(f"Line {self.line_num}:", _INDENT),
|
616
|
-
indent(self.code_line, 2 * _INDENT),
|
617
|
-
])
|
618
|
-
if error is not None:
|
619
|
-
lines.extend([
|
620
|
-
indent("Raised:", _INDENT),
|
621
|
-
_format_exception(error, depth=2),
|
622
|
-
])
|
623
|
-
return indent("\n".join(lines), depth * _INDENT)
|
624
|
-
|
625
|
-
|
626
|
-
##
|
627
|
-
|
628
|
-
|
629
|
-
def get_rich_traceback(
|
630
|
-
error: TBaseException,
|
631
|
-
/,
|
632
|
-
*,
|
633
|
-
start: MaybeCallableDateTime | None = _START,
|
634
|
-
version: MaybeCallableVersionLike | None = None,
|
635
|
-
max_width: int = RICH_MAX_WIDTH,
|
636
|
-
indent_size: int = RICH_INDENT_SIZE,
|
637
|
-
max_length: int | None = RICH_MAX_LENGTH,
|
638
|
-
max_string: int | None = RICH_MAX_STRING,
|
639
|
-
max_depth: int | None = RICH_MAX_DEPTH,
|
640
|
-
expand_all: bool = RICH_EXPAND_ALL,
|
641
|
-
) -> (
|
642
|
-
ExcChainTB[TBaseException]
|
643
|
-
| ExcGroupTB[TBaseException]
|
644
|
-
| ExcTB[TBaseException]
|
645
|
-
| TBaseException
|
646
|
-
):
|
647
|
-
"""Get a rich traceback."""
|
648
|
-
match list(yield_exceptions(error)):
|
649
|
-
case []: # pragma: no cover
|
650
|
-
raise ImpossibleCaseError(case=[f"{error}"])
|
651
|
-
case [err]:
|
652
|
-
err_recast = cast("TBaseException", err)
|
653
|
-
return _get_rich_traceback_non_chain(
|
654
|
-
err_recast,
|
655
|
-
start=start,
|
656
|
-
version=version,
|
657
|
-
max_width=max_width,
|
658
|
-
indent_size=indent_size,
|
659
|
-
max_length=max_length,
|
660
|
-
max_string=max_string,
|
661
|
-
max_depth=max_depth,
|
662
|
-
expand_all=expand_all,
|
663
|
-
)
|
664
|
-
case errs:
|
665
|
-
errs_recast = cast("list[TBaseException]", errs)
|
666
|
-
return ExcChainTB(
|
667
|
-
errors=[
|
668
|
-
_get_rich_traceback_non_chain(
|
669
|
-
e,
|
670
|
-
start=start,
|
671
|
-
version=version,
|
672
|
-
max_width=max_width,
|
673
|
-
indent_size=indent_size,
|
674
|
-
max_length=max_length,
|
675
|
-
max_string=max_string,
|
676
|
-
max_depth=max_depth,
|
677
|
-
expand_all=expand_all,
|
678
|
-
)
|
679
|
-
for e in errs_recast
|
680
|
-
],
|
681
|
-
start=start,
|
682
|
-
version=version,
|
683
|
-
max_width=max_width,
|
684
|
-
indent_size=indent_size,
|
685
|
-
max_length=max_length,
|
686
|
-
max_string=max_string,
|
687
|
-
max_depth=max_depth,
|
688
|
-
expand_all=expand_all,
|
689
|
-
)
|
690
|
-
|
691
|
-
|
692
|
-
def _get_rich_traceback_non_chain(
|
693
|
-
error: ExceptionGroup[Any] | TBaseException,
|
694
|
-
/,
|
695
|
-
*,
|
696
|
-
start: MaybeCallableDateTime | None = _START,
|
697
|
-
version: MaybeCallableVersionLike | None = None,
|
698
|
-
max_width: int = RICH_MAX_WIDTH,
|
699
|
-
indent_size: int = RICH_INDENT_SIZE,
|
700
|
-
max_length: int | None = RICH_MAX_LENGTH,
|
701
|
-
max_string: int | None = RICH_MAX_STRING,
|
702
|
-
max_depth: int | None = RICH_MAX_DEPTH,
|
703
|
-
expand_all: bool = RICH_EXPAND_ALL,
|
704
|
-
) -> ExcGroupTB[TBaseException] | ExcTB[TBaseException] | TBaseException:
|
705
|
-
"""Get a rich traceback, for a non-chained error."""
|
706
|
-
match error:
|
707
|
-
case ExceptionGroup() as exc_group: # skipif-ci
|
708
|
-
exc_group_or_exc_tb = _get_rich_traceback_base_one(
|
709
|
-
exc_group,
|
710
|
-
max_width=max_width,
|
711
|
-
indent_size=indent_size,
|
712
|
-
max_length=max_length,
|
713
|
-
max_string=max_string,
|
714
|
-
max_depth=max_depth,
|
715
|
-
expand_all=expand_all,
|
716
|
-
)
|
717
|
-
errors = [
|
718
|
-
_get_rich_traceback_non_chain(
|
719
|
-
e,
|
720
|
-
start=start,
|
721
|
-
version=version,
|
722
|
-
max_width=max_width,
|
723
|
-
indent_size=indent_size,
|
724
|
-
max_length=max_length,
|
725
|
-
max_string=max_string,
|
726
|
-
max_depth=max_depth,
|
727
|
-
expand_all=expand_all,
|
728
|
-
)
|
729
|
-
for e in always_iterable(exc_group.exceptions)
|
730
|
-
]
|
731
|
-
return ExcGroupTB(
|
732
|
-
exc_group=exc_group_or_exc_tb,
|
733
|
-
errors=errors,
|
734
|
-
start=start,
|
735
|
-
version=version,
|
736
|
-
max_width=max_width,
|
737
|
-
indent_size=indent_size,
|
738
|
-
max_length=max_length,
|
739
|
-
max_string=max_string,
|
740
|
-
max_depth=max_depth,
|
741
|
-
expand_all=expand_all,
|
742
|
-
)
|
743
|
-
case BaseException() as base_exc:
|
744
|
-
return _get_rich_traceback_base_one(
|
745
|
-
base_exc,
|
746
|
-
start=start,
|
747
|
-
version=version,
|
748
|
-
max_width=max_width,
|
749
|
-
indent_size=indent_size,
|
750
|
-
max_length=max_length,
|
751
|
-
max_string=max_string,
|
752
|
-
max_depth=max_depth,
|
753
|
-
expand_all=expand_all,
|
754
|
-
)
|
755
|
-
case _ as never:
|
756
|
-
assert_never(never)
|
757
|
-
|
758
|
-
|
759
|
-
def _get_rich_traceback_base_one(
|
760
|
-
error: TBaseException,
|
761
|
-
/,
|
762
|
-
*,
|
763
|
-
start: MaybeCallableDateTime | None = _START,
|
764
|
-
version: MaybeCallableVersionLike | None = None,
|
765
|
-
max_width: int = RICH_MAX_WIDTH,
|
766
|
-
indent_size: int = RICH_INDENT_SIZE,
|
767
|
-
max_length: int | None = RICH_MAX_LENGTH,
|
768
|
-
max_string: int | None = RICH_MAX_STRING,
|
769
|
-
max_depth: int | None = RICH_MAX_DEPTH,
|
770
|
-
expand_all: bool = RICH_EXPAND_ALL,
|
771
|
-
) -> ExcTB[TBaseException] | TBaseException:
|
772
|
-
"""Get a rich traceback, for a single exception."""
|
773
|
-
if isinstance(error, _HasExceptionPath):
|
774
|
-
frames = [
|
775
|
-
_Frame(
|
776
|
-
module=f.module,
|
777
|
-
name=f.name,
|
778
|
-
code_line=f.code_line,
|
779
|
-
line_num=f.line_num,
|
780
|
-
args=f.extra.args,
|
781
|
-
kwargs=f.extra.kwargs,
|
782
|
-
locals=f.locals,
|
783
|
-
max_width=max_width,
|
784
|
-
indent_size=indent_size,
|
785
|
-
max_length=max_length,
|
786
|
-
max_string=max_string,
|
787
|
-
max_depth=max_depth,
|
788
|
-
expand_all=expand_all,
|
789
|
-
)
|
790
|
-
for f in error.exc_tb.frames
|
791
|
-
]
|
792
|
-
return ExcTB(
|
793
|
-
frames=frames,
|
794
|
-
error=error,
|
795
|
-
start=start,
|
796
|
-
version=version,
|
797
|
-
max_width=max_width,
|
798
|
-
indent_size=indent_size,
|
799
|
-
max_length=max_length,
|
800
|
-
max_string=max_string,
|
801
|
-
max_depth=max_depth,
|
802
|
-
expand_all=expand_all,
|
803
|
-
)
|
804
|
-
return error
|
805
|
-
|
806
|
-
|
807
|
-
def trace(func: TCallable, /) -> TCallable:
|
808
|
-
"""Trace a function call."""
|
809
|
-
match bool(iscoroutinefunction(func)):
|
810
|
-
case False:
|
811
|
-
func_typed = cast("Callable[..., Any]", func)
|
812
|
-
|
813
|
-
@wraps(func)
|
814
|
-
def trace_sync(*args: Any, **kwargs: Any) -> Any:
|
815
|
-
locals()[_CALL_ARGS] = _CallArgs.create(func, *args, **kwargs)
|
816
|
-
try:
|
817
|
-
return func_typed(*args, **kwargs)
|
818
|
-
except Exception as error:
|
819
|
-
cast("Any", error).exc_tb = _get_rich_traceback_internal(error)
|
820
|
-
raise
|
821
|
-
|
822
|
-
return cast("TCallable", trace_sync)
|
823
|
-
case True:
|
824
|
-
func_typed = cast("Callable[..., Coroutine1[Any]]", func)
|
825
|
-
|
826
|
-
@wraps(func)
|
827
|
-
async def trace_async(*args: Any, **kwargs: Any) -> Any:
|
828
|
-
locals()[_CALL_ARGS] = _CallArgs.create(func, *args, **kwargs)
|
829
|
-
try: # skipif-ci
|
830
|
-
return await func_typed(*args, **kwargs)
|
831
|
-
except Exception as error: # skipif-ci
|
832
|
-
cast("Any", error).exc_tb = _get_rich_traceback_internal(error)
|
833
|
-
raise
|
834
|
-
|
835
|
-
return cast("TCallable", trace_async)
|
836
|
-
case _ as never:
|
837
|
-
assert_never(never)
|
838
|
-
|
839
|
-
|
840
|
-
@overload
|
841
|
-
def yield_extended_frame_summaries(
|
842
|
-
error: BaseException, /, *, extra: Callable[[FrameSummary, FrameType], _T]
|
843
|
-
) -> Iterator[_ExtFrameSummary[_T]]: ...
|
844
|
-
@overload
|
845
|
-
def yield_extended_frame_summaries(
|
846
|
-
error: BaseException, /, *, extra: None = None
|
847
|
-
) -> Iterator[_ExtFrameSummary[None]]: ...
|
848
|
-
def yield_extended_frame_summaries(
|
849
|
-
error: BaseException,
|
850
|
-
/,
|
851
|
-
*,
|
852
|
-
extra: Callable[[FrameSummary, FrameType], _T] | None = None,
|
853
|
-
) -> Iterator[_ExtFrameSummary[Any]]:
|
854
|
-
"""Yield the extended frame summaries."""
|
855
|
-
tb_exc = TracebackException.from_exception(error, capture_locals=True)
|
856
|
-
_, _, traceback = exc_info()
|
857
|
-
frames = yield_frames(traceback=traceback)
|
858
|
-
for summary, frame in zip(tb_exc.stack, frames, strict=True):
|
859
|
-
if extra is None:
|
860
|
-
extra_use: _T | None = None
|
861
|
-
else:
|
862
|
-
extra_use: _T | None = extra(summary, frame)
|
863
|
-
yield _ExtFrameSummary(
|
864
|
-
filename=Path(summary.filename),
|
865
|
-
module=frame.f_globals.get("__name__"),
|
866
|
-
name=summary.name,
|
867
|
-
qualname=frame.f_code.co_qualname,
|
868
|
-
code_line=ensure_not_none(summary.line, desc="summary.line"),
|
869
|
-
first_line_num=frame.f_code.co_firstlineno,
|
870
|
-
line_num=ensure_not_none(summary.lineno, desc="summary.lineno"),
|
871
|
-
end_line_num=ensure_not_none(summary.end_lineno, desc="summary.end_lineno"),
|
872
|
-
col_num=summary.colno,
|
873
|
-
end_col_num=summary.end_colno,
|
874
|
-
locals=frame.f_locals,
|
875
|
-
extra=extra_use,
|
876
|
-
)
|
877
|
-
|
878
|
-
|
879
|
-
def yield_exceptions(error: BaseException, /) -> Iterator[BaseException]:
|
880
|
-
"""Yield the exceptions in a context chain."""
|
881
|
-
curr: BaseException | None = error
|
882
|
-
while curr is not None:
|
883
|
-
yield curr
|
884
|
-
curr = curr.__context__
|
885
|
-
|
886
|
-
|
887
|
-
def yield_frames(*, traceback: TracebackType | None = None) -> Iterator[FrameType]:
|
888
|
-
"""Yield the frames of a traceback."""
|
889
|
-
while traceback is not None:
|
890
|
-
yield traceback.tb_frame
|
891
|
-
traceback = traceback.tb_next
|
892
|
-
|
893
|
-
|
894
|
-
def _format_exception(error: BaseException, /, *, depth: int = 0) -> str:
|
895
|
-
"""Format an exception."""
|
896
|
-
name = get_class_name(error, qual=True)
|
897
|
-
line = f"{name}({error})"
|
898
|
-
return indent(line, depth * _INDENT)
|
899
|
-
|
900
|
-
|
901
|
-
def _get_rich_traceback_internal(error: BaseException, /) -> _ExcTBInternal:
|
902
|
-
"""Get a rich traceback; for internal use only."""
|
903
|
-
|
904
|
-
def extra(_: FrameSummary, frame: FrameType) -> _CallArgs | None:
|
905
|
-
return frame.f_locals.get(_CALL_ARGS)
|
906
|
-
|
907
|
-
raw = list(yield_extended_frame_summaries(error, extra=extra))
|
908
|
-
return _ExcTBInternal(raw=raw, frames=_merge_frames(raw), error=error)
|
909
|
-
|
910
|
-
|
911
|
-
def _merge_frames(
|
912
|
-
frames: Iterable[_ExtFrameSummaryCAOpt], /
|
913
|
-
) -> list[_ExtFrameSummaryCA]:
|
914
|
-
"""Merge a set of frames."""
|
915
|
-
rev = list(frames)[::-1]
|
916
|
-
values: list[_ExtFrameSummaryCA] = []
|
917
|
-
|
918
|
-
def get_solution(
|
919
|
-
curr: _ExtFrameSummaryCAOpt, rev: list[_ExtFrameSummaryCAOpt], /
|
920
|
-
) -> _ExtFrameSummaryCA:
|
921
|
-
while True:
|
922
|
-
next_ = rev.pop(0)
|
923
|
-
if has_extra(next_) and is_match(curr, next_):
|
924
|
-
return next_
|
925
|
-
|
926
|
-
def has_extra(frame: _ExtFrameSummaryCAOpt, /) -> TypeGuard[_ExtFrameSummaryCA]:
|
927
|
-
return frame.extra is not None
|
928
|
-
|
929
|
-
def has_match(
|
930
|
-
curr: _ExtFrameSummaryCAOpt, rev: list[_ExtFrameSummaryCAOpt], /
|
931
|
-
) -> bool:
|
932
|
-
next_, *_ = filter(has_extra, rev)
|
933
|
-
return is_match(curr, next_)
|
934
|
-
|
935
|
-
def is_match(curr: _ExtFrameSummaryCAOpt, next_: _ExtFrameSummaryCA, /) -> bool:
|
936
|
-
return (curr.name == next_.extra.func.__name__) and (
|
937
|
-
(curr.module is None) or (curr.module == next_.extra.func.__module__)
|
938
|
-
)
|
939
|
-
|
940
|
-
while len(rev) >= 1:
|
941
|
-
curr = rev.pop(0)
|
942
|
-
if not has_match(curr, rev):
|
943
|
-
continue
|
944
|
-
next_ = get_solution(curr, rev)
|
945
|
-
new = cast("_ExtFrameSummaryCA", replace(curr, extra=next_.extra))
|
946
|
-
values.append(new)
|
947
|
-
return values[::-1]
|
948
|
-
|
949
|
-
|
950
|
-
##
|
951
|
-
|
952
|
-
|
953
81
|
def _yield_header_lines(
|
954
82
|
*,
|
955
83
|
start: MaybeCallableDateTime | None = _START,
|
@@ -977,9 +105,6 @@ def _yield_header_lines(
|
|
977
105
|
yield ""
|
978
106
|
|
979
107
|
|
980
|
-
##
|
981
|
-
|
982
|
-
|
983
108
|
def _yield_formatted_frame_summary(
|
984
109
|
error: BaseException,
|
985
110
|
/,
|
@@ -1064,6 +189,94 @@ def _trim_path(path: PathLike, pattern: str, /) -> Path | None:
|
|
1064
189
|
return Path(*parts[i + 1 :])
|
1065
190
|
|
1066
191
|
|
192
|
+
##
|
193
|
+
|
194
|
+
|
195
|
+
def make_except_hook(
|
196
|
+
*,
|
197
|
+
start: MaybeCallableDateTime | None = _START,
|
198
|
+
version: MaybeCallableVersionLike | None = None,
|
199
|
+
path: MaybeCallablePathLike | None = None,
|
200
|
+
max_width: int = RICH_MAX_WIDTH,
|
201
|
+
indent_size: int = RICH_INDENT_SIZE,
|
202
|
+
max_length: int | None = RICH_MAX_LENGTH,
|
203
|
+
max_string: int | None = RICH_MAX_STRING,
|
204
|
+
max_depth: int | None = RICH_MAX_DEPTH,
|
205
|
+
expand_all: bool = RICH_EXPAND_ALL,
|
206
|
+
slack_url: str | None = None,
|
207
|
+
) -> Callable[
|
208
|
+
[type[BaseException] | None, BaseException | None, TracebackType | None], None
|
209
|
+
]:
|
210
|
+
"""Exception hook to log the traceback."""
|
211
|
+
return partial(
|
212
|
+
_make_except_hook_inner,
|
213
|
+
start=start,
|
214
|
+
version=version,
|
215
|
+
path=path,
|
216
|
+
max_width=max_width,
|
217
|
+
indent_size=indent_size,
|
218
|
+
max_length=max_length,
|
219
|
+
max_string=max_string,
|
220
|
+
max_depth=max_depth,
|
221
|
+
expand_all=expand_all,
|
222
|
+
slack_url=slack_url,
|
223
|
+
)
|
224
|
+
|
225
|
+
|
226
|
+
def _make_except_hook_inner(
|
227
|
+
exc_type: type[BaseException] | None,
|
228
|
+
exc_val: BaseException | None,
|
229
|
+
traceback: TracebackType | None,
|
230
|
+
/,
|
231
|
+
*,
|
232
|
+
start: MaybeCallableDateTime | None = _START,
|
233
|
+
version: MaybeCallableVersionLike | None = None,
|
234
|
+
path: MaybeCallablePathLike | None = None,
|
235
|
+
max_width: int = RICH_MAX_WIDTH,
|
236
|
+
indent_size: int = RICH_INDENT_SIZE,
|
237
|
+
max_length: int | None = RICH_MAX_LENGTH,
|
238
|
+
max_string: int | None = RICH_MAX_STRING,
|
239
|
+
max_depth: int | None = RICH_MAX_DEPTH,
|
240
|
+
expand_all: bool = RICH_EXPAND_ALL,
|
241
|
+
slack_url: str | None = None,
|
242
|
+
) -> None:
|
243
|
+
"""Exception hook to log the traceback."""
|
244
|
+
_ = (exc_type, traceback)
|
245
|
+
if exc_val is None:
|
246
|
+
raise MakeExceptHookError
|
247
|
+
slim = format_exception_stack(exc_val, header=True, start=start, version=version)
|
248
|
+
_ = sys.stderr.write(f"{slim}\n") # don't 'from sys import stderr'
|
249
|
+
if path is not None:
|
250
|
+
from utilities.atomicwrites import writer
|
251
|
+
from utilities.tzlocal import get_now_local
|
252
|
+
|
253
|
+
path = (
|
254
|
+
get_path(path=path)
|
255
|
+
.joinpath(serialize_compact(get_now_local()))
|
256
|
+
.with_suffix(".txt")
|
257
|
+
)
|
258
|
+
full = format_exception_stack(
|
259
|
+
exc_val,
|
260
|
+
header=True,
|
261
|
+
start=start,
|
262
|
+
version=version,
|
263
|
+
capture_locals=True,
|
264
|
+
max_width=max_width,
|
265
|
+
indent_size=indent_size,
|
266
|
+
max_length=max_length,
|
267
|
+
max_string=max_string,
|
268
|
+
max_depth=max_depth,
|
269
|
+
expand_all=expand_all,
|
270
|
+
)
|
271
|
+
with writer(path, overwrite=True) as temp:
|
272
|
+
_ = temp.write_text(full)
|
273
|
+
if slack_url is not None: # pragma: no cover
|
274
|
+
from utilities.slack_sdk import send_to_slack
|
275
|
+
|
276
|
+
send = f"```{slim}```"
|
277
|
+
run(send_to_slack(slack_url, send))
|
278
|
+
|
279
|
+
|
1067
280
|
@dataclass(kw_only=True, slots=True)
|
1068
281
|
class MakeExceptHookError(Exception):
|
1069
282
|
@override
|
@@ -1071,16 +284,4 @@ class MakeExceptHookError(Exception):
|
|
1071
284
|
return "No exception to log"
|
1072
285
|
|
1073
286
|
|
1074
|
-
__all__ = [
|
1075
|
-
"ExcChainTB",
|
1076
|
-
"ExcGroupTB",
|
1077
|
-
"ExcTB",
|
1078
|
-
"RichTracebackFormatter",
|
1079
|
-
"format_exception_stack",
|
1080
|
-
"get_rich_traceback",
|
1081
|
-
"make_except_hook",
|
1082
|
-
"trace",
|
1083
|
-
"yield_exceptions",
|
1084
|
-
"yield_extended_frame_summaries",
|
1085
|
-
"yield_frames",
|
1086
|
-
]
|
287
|
+
__all__ = ["format_exception_stack", "make_except_hook"]
|