errortools 2.4.0__tar.gz → 2.5.0__tar.gz
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.
- {errortools-2.4.0/errortools.egg-info → errortools-2.5.0}/PKG-INFO +13 -4
- {errortools-2.4.0 → errortools-2.5.0}/README.md +11 -3
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/classes/abc.py +6 -1
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/decorator/deprecated.py +1 -1
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/future.py +165 -165
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/logging/sink.py +2 -2
- errortools-2.5.0/_errortools/partial.py +199 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/version.py +2 -2
- errortools-2.5.0/errortools/future.py +5 -0
- errortools-2.5.0/errortools/logging.py +46 -0
- errortools-2.5.0/errortools/partial.py +5 -0
- {errortools-2.4.0 → errortools-2.5.0/errortools.egg-info}/PKG-INFO +13 -4
- {errortools-2.4.0 → errortools-2.5.0}/errortools.egg-info/SOURCES.txt +3 -0
- errortools-2.5.0/setup.py +79 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/__init__.py +1 -1
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_decorator.py +3 -3
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_logging.py +7 -5
- errortools-2.4.0/_errortools/partial.py +0 -110
- errortools-2.4.0/setup.py +0 -35
- {errortools-2.4.0 → errortools-2.5.0}/AUTHORS.txt +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/LICENSE.txt +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/__init__.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/_cli.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/classes/__init__.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/classes/errorcodes.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/classes/group.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/classes/warn.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/cli.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/const.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/decorator/__init__.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/decorator/cache.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/descriptor/__init__.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/descriptor/errormsg.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/descriptor/nonblankmsg.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/errno.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/ignore.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/logging/__init__.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/logging/base.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/logging/level.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/logging/logger.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/logging/record.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/metadata.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/methods/__init__.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/methods/errorattr.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/methods/errordelattr.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/methods/errorhasattr.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/methods/errorsetattr.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/py.typed +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/raises.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/typing.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/wrappers/__init__.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/wrappers/cache.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/_errortools/wrappers/ignore.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/errortools/__init__.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/errortools/__main__.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/errortools.egg-info/dependency_links.txt +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/errortools.egg-info/entry_points.txt +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/errortools.egg-info/requires.txt +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/errortools.egg-info/top_level.txt +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/setup.cfg +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/conftest.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/run_tests.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_abc.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_const.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_descriptor.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_errno.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_errorcodes.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_future.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_groups.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_ignore.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_mixins.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_partials.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_raises.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_typing.py +0 -0
- {errortools-2.4.0 → errortools-2.5.0}/tests/test_warnings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: errortools
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: errortools - a toolset for working with Python exceptions and warnings and logging.
|
|
5
5
|
Home-page: https://github.com/more-abc/errortools
|
|
6
6
|
Author: Evan Yang
|
|
@@ -13,6 +13,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
16
17
|
Classifier: Operating System :: OS Independent
|
|
17
18
|
Classifier: Typing :: Typed
|
|
18
19
|
Requires-Python: >=3.10
|
|
@@ -36,6 +37,14 @@ Dynamic: summary
|
|
|
36
37
|
# errortools
|
|
37
38
|
A lightweight Python exception handling utility library.
|
|
38
39
|
|
|
40
|
+
[](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
|
|
41
|
+
[](https://pypi.org/project/errortools/)
|
|
42
|
+
[](https://pypi.org/project/errortools/)
|
|
43
|
+

|
|
44
|
+

|
|
45
|
+

|
|
46
|
+

|
|
47
|
+
|
|
39
48
|
## Features
|
|
40
49
|
- **Raise Exceptions**: `raises()`, `raises_all()`, `reraise()` — batch raising and exception conversion
|
|
41
50
|
- **Catch & Suppress**: `ignore()`, `ignore_subclass()`, `ignore_warns()`, `fast_ignore()`, `super_fast_ignore()`, `timeout()`, `retry()` — graceful suppression of exceptions and warnings, with automatic retry
|
|
@@ -255,9 +264,9 @@ logger.success("All systems operational")
|
|
|
255
264
|
Output (colourised in a terminal):
|
|
256
265
|
|
|
257
266
|
```
|
|
258
|
-
2026-
|
|
259
|
-
2026-
|
|
260
|
-
2026-
|
|
267
|
+
2026-04-30 08:34:21.850 | ℹ INFO | <string>:<module>:3 - Server started on port 8080
|
|
268
|
+
2026-04-30 08:34:21.851 | ⚠ WARNING | <string>:<module>:4 - Disk at 92.5%
|
|
269
|
+
2026-04-30 08:34:21.851 | ✔ SUCCESS | <string>:<module>:5 - All systems operational
|
|
261
270
|
```
|
|
262
271
|
|
|
263
272
|
### Log levels
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# errortools
|
|
2
2
|
A lightweight Python exception handling utility library.
|
|
3
3
|
|
|
4
|
+
[](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
|
|
5
|
+
[](https://pypi.org/project/errortools/)
|
|
6
|
+
[](https://pypi.org/project/errortools/)
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+
|
|
4
12
|
## Features
|
|
5
13
|
- **Raise Exceptions**: `raises()`, `raises_all()`, `reraise()` — batch raising and exception conversion
|
|
6
14
|
- **Catch & Suppress**: `ignore()`, `ignore_subclass()`, `ignore_warns()`, `fast_ignore()`, `super_fast_ignore()`, `timeout()`, `retry()` — graceful suppression of exceptions and warnings, with automatic retry
|
|
@@ -220,9 +228,9 @@ logger.success("All systems operational")
|
|
|
220
228
|
Output (colourised in a terminal):
|
|
221
229
|
|
|
222
230
|
```
|
|
223
|
-
2026-
|
|
224
|
-
2026-
|
|
225
|
-
2026-
|
|
231
|
+
2026-04-30 08:34:21.850 | ℹ INFO | <string>:<module>:3 - Server started on port 8080
|
|
232
|
+
2026-04-30 08:34:21.851 | ⚠ WARNING | <string>:<module>:4 - Disk at 92.5%
|
|
233
|
+
2026-04-30 08:34:21.851 | ✔ SUCCESS | <string>:<module>:5 - All systems operational
|
|
226
234
|
```
|
|
227
235
|
|
|
228
236
|
### Log levels
|
|
@@ -4,8 +4,13 @@ import copy
|
|
|
4
4
|
import shutil
|
|
5
5
|
import csv
|
|
6
6
|
import configparser
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
if sys.version_info >= (3, 15):
|
|
10
|
+
from typing import disjoint_base
|
|
11
|
+
else:
|
|
12
|
+
from typing_extensions import disjoint_base
|
|
7
13
|
|
|
8
|
-
from typing_extensions import disjoint_base # I use 3.14.3
|
|
9
14
|
from ..methods import (
|
|
10
15
|
ErrorAttrMixin,
|
|
11
16
|
ErrorAttrCheckMixin,
|
|
@@ -53,7 +53,7 @@ def experimental(
|
|
|
53
53
|
@wraps(func)
|
|
54
54
|
def wrapper(*args, **kwargs):
|
|
55
55
|
msg = f"{func.__name__} is experimental. {reason}"
|
|
56
|
-
warnings.warn(msg,
|
|
56
|
+
warnings.warn(msg, FutureWarning, stacklevel=2)
|
|
57
57
|
return func(*args, **kwargs)
|
|
58
58
|
|
|
59
59
|
return wrapper
|
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
"""Future-focused lightweight exception handling utilities."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TypeAlias, cast, Literal
|
|
6
|
-
|
|
7
|
-
__all__ = [
|
|
8
|
-
"super_fast_ignore",
|
|
9
|
-
"super_fast_catch",
|
|
10
|
-
"super_fast_reraise",
|
|
11
|
-
"ExceptionCollector",
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
_ExcType: TypeAlias = type[BaseException]
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class super_fast_ignore:
|
|
18
|
-
"""Ultra-lightweight context manager to suppress exceptions."""
|
|
19
|
-
|
|
20
|
-
__slots__ = ("excs",)
|
|
21
|
-
|
|
22
|
-
def __init__(self, *excs: _ExcType) -> None:
|
|
23
|
-
self.excs = excs
|
|
24
|
-
|
|
25
|
-
def __enter__(self) -> None:
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
def __exit__(self, typ: _ExcType | None, *_) -> bool:
|
|
29
|
-
if typ is None:
|
|
30
|
-
return False
|
|
31
|
-
excs = self.excs
|
|
32
|
-
return issubclass(typ, excs)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class super_fast_catch:
|
|
36
|
-
"""Ultra-lightweight context manager to catch and store exceptions.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
*excs: Exception types to catch. Empty means catch all.
|
|
40
|
-
|
|
41
|
-
Example:
|
|
42
|
-
>>> with super_fast_catch(ValueError) as ctx:
|
|
43
|
-
... raise ValueError("oops")
|
|
44
|
-
>>> print(ctx.exception)
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
__slots__ = ("excs", "exception")
|
|
48
|
-
|
|
49
|
-
def __init__(self, *excs: _ExcType) -> None:
|
|
50
|
-
self.excs = excs if excs else (BaseException,)
|
|
51
|
-
self.exception: BaseException | None = None
|
|
52
|
-
|
|
53
|
-
def __enter__(self) -> "super_fast_catch":
|
|
54
|
-
return self
|
|
55
|
-
|
|
56
|
-
def __exit__(self, typ: _ExcType | None, val, *_) -> bool:
|
|
57
|
-
if typ is None or not issubclass(typ, self.excs):
|
|
58
|
-
return False
|
|
59
|
-
self.exception = val
|
|
60
|
-
return True
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class super_fast_reraise:
|
|
64
|
-
"""Ultra-lightweight context manager to convert exception types.
|
|
65
|
-
|
|
66
|
-
Args:
|
|
67
|
-
catch: Exception type(s) to intercept.
|
|
68
|
-
raise_as: Exception type to raise instead.
|
|
69
|
-
|
|
70
|
-
Example:
|
|
71
|
-
>>> with super_fast_reraise(KeyError, ValueError):
|
|
72
|
-
... raise KeyError("missing")
|
|
73
|
-
>>> # Raises ValueError: missing
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
|
-
__slots__ = ("catch", "raise_as")
|
|
77
|
-
|
|
78
|
-
def __init__(
|
|
79
|
-
self,
|
|
80
|
-
catch: _ExcType | tuple[_ExcType, ...],
|
|
81
|
-
raise_as: _ExcType,
|
|
82
|
-
) -> None:
|
|
83
|
-
self.catch = catch if isinstance(catch, tuple) else (catch,)
|
|
84
|
-
self.raise_as = raise_as
|
|
85
|
-
|
|
86
|
-
def __enter__(self) -> None:
|
|
87
|
-
return
|
|
88
|
-
|
|
89
|
-
def __exit__(self, typ: _ExcType | None, val, *_) -> Literal[False]:
|
|
90
|
-
if typ is None or not issubclass(typ, self.catch):
|
|
91
|
-
return False
|
|
92
|
-
raise self.raise_as(str(val)) from val
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class ExceptionCollector:
|
|
96
|
-
"""Collect multiple exceptions and raise together at the end.
|
|
97
|
-
|
|
98
|
-
Useful for batch operations where you want all errors, not just the first.
|
|
99
|
-
|
|
100
|
-
Example:
|
|
101
|
-
>>> collector = ExceptionCollector()
|
|
102
|
-
>>> with collector:
|
|
103
|
-
... collector.catch(int, "bad1")
|
|
104
|
-
... collector.catch(int, "bad2")
|
|
105
|
-
>>> if collector.has_errors:
|
|
106
|
-
... collector.raise_all()
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
|
-
__slots__ = ("_exceptions", "_stop_on_first")
|
|
110
|
-
|
|
111
|
-
def __init__(self, stop_on_first: bool = False) -> None:
|
|
112
|
-
self._exceptions: list[BaseException] = []
|
|
113
|
-
self._stop_on_first = stop_on_first
|
|
114
|
-
|
|
115
|
-
def __enter__(self) -> ExceptionCollector:
|
|
116
|
-
return self
|
|
117
|
-
|
|
118
|
-
def __exit__(self, exc_typ, exc_val, *_) -> bool:
|
|
119
|
-
if exc_typ is not None:
|
|
120
|
-
self._exceptions.append(exc_val)
|
|
121
|
-
if self._stop_on_first:
|
|
122
|
-
return False
|
|
123
|
-
return True
|
|
124
|
-
return False
|
|
125
|
-
|
|
126
|
-
def catch(self, func, *args, **kwargs) -> bool:
|
|
127
|
-
"""Try to call func and catch any exception. Returns True if exception caught."""
|
|
128
|
-
try:
|
|
129
|
-
func(*args, **kwargs)
|
|
130
|
-
return False
|
|
131
|
-
except BaseException as exc:
|
|
132
|
-
self._exceptions.append(exc)
|
|
133
|
-
if self._stop_on_first:
|
|
134
|
-
raise
|
|
135
|
-
return True
|
|
136
|
-
|
|
137
|
-
def add(self, exc: BaseException) -> None:
|
|
138
|
-
"""Manually add an exception."""
|
|
139
|
-
self._exceptions.append(exc)
|
|
140
|
-
if self._stop_on_first:
|
|
141
|
-
raise exc
|
|
142
|
-
|
|
143
|
-
@property
|
|
144
|
-
def has_errors(self) -> bool:
|
|
145
|
-
"""Check if any exceptions were collected."""
|
|
146
|
-
return len(self._exceptions) > 0
|
|
147
|
-
|
|
148
|
-
@property
|
|
149
|
-
def count(self) -> int:
|
|
150
|
-
"""Get number of collected exceptions."""
|
|
151
|
-
return len(self._exceptions)
|
|
152
|
-
|
|
153
|
-
@property
|
|
154
|
-
def exceptions(self) -> list[BaseException]:
|
|
155
|
-
"""Get the list of exceptions."""
|
|
156
|
-
return self._exceptions
|
|
157
|
-
|
|
158
|
-
def raise_all(self, message: str = "collected errors") -> None:
|
|
159
|
-
"""Raise all collected exceptions as ExceptionGroup."""
|
|
160
|
-
if self._exceptions:
|
|
161
|
-
raise ExceptionGroup(message, cast(list[Exception], self._exceptions))
|
|
162
|
-
|
|
163
|
-
def clear(self) -> None:
|
|
164
|
-
"""Clear all exceptions."""
|
|
165
|
-
self._exceptions.clear()
|
|
1
|
+
"""Future-focused lightweight exception handling utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TypeAlias, cast, Literal
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"super_fast_ignore",
|
|
9
|
+
"super_fast_catch",
|
|
10
|
+
"super_fast_reraise",
|
|
11
|
+
"ExceptionCollector",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
_ExcType: TypeAlias = type[BaseException]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class super_fast_ignore:
|
|
18
|
+
"""Ultra-lightweight context manager to suppress exceptions."""
|
|
19
|
+
|
|
20
|
+
__slots__ = ("excs",)
|
|
21
|
+
|
|
22
|
+
def __init__(self, *excs: _ExcType) -> None:
|
|
23
|
+
self.excs = excs
|
|
24
|
+
|
|
25
|
+
def __enter__(self) -> None:
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
def __exit__(self, typ: _ExcType | None, *_) -> bool:
|
|
29
|
+
if typ is None:
|
|
30
|
+
return False
|
|
31
|
+
excs = self.excs
|
|
32
|
+
return issubclass(typ, excs)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class super_fast_catch:
|
|
36
|
+
"""Ultra-lightweight context manager to catch and store exceptions.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
*excs: Exception types to catch. Empty means catch all.
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
>>> with super_fast_catch(ValueError) as ctx:
|
|
43
|
+
... raise ValueError("oops")
|
|
44
|
+
>>> print(ctx.exception)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
__slots__ = ("excs", "exception")
|
|
48
|
+
|
|
49
|
+
def __init__(self, *excs: _ExcType) -> None:
|
|
50
|
+
self.excs = excs if excs else (BaseException,)
|
|
51
|
+
self.exception: BaseException | None = None
|
|
52
|
+
|
|
53
|
+
def __enter__(self) -> "super_fast_catch":
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
def __exit__(self, typ: _ExcType | None, val, *_) -> bool:
|
|
57
|
+
if typ is None or not issubclass(typ, self.excs):
|
|
58
|
+
return False
|
|
59
|
+
self.exception = val
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class super_fast_reraise:
|
|
64
|
+
"""Ultra-lightweight context manager to convert exception types.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
catch: Exception type(s) to intercept.
|
|
68
|
+
raise_as: Exception type to raise instead.
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
>>> with super_fast_reraise(KeyError, ValueError):
|
|
72
|
+
... raise KeyError("missing")
|
|
73
|
+
>>> # Raises ValueError: missing
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
__slots__ = ("catch", "raise_as")
|
|
77
|
+
|
|
78
|
+
def __init__(
|
|
79
|
+
self,
|
|
80
|
+
catch: _ExcType | tuple[_ExcType, ...],
|
|
81
|
+
raise_as: _ExcType,
|
|
82
|
+
) -> None:
|
|
83
|
+
self.catch = catch if isinstance(catch, tuple) else (catch,)
|
|
84
|
+
self.raise_as = raise_as
|
|
85
|
+
|
|
86
|
+
def __enter__(self) -> None:
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
def __exit__(self, typ: _ExcType | None, val, *_) -> Literal[False]:
|
|
90
|
+
if typ is None or not issubclass(typ, self.catch):
|
|
91
|
+
return False
|
|
92
|
+
raise self.raise_as(str(val)) from val
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ExceptionCollector:
|
|
96
|
+
"""Collect multiple exceptions and raise together at the end.
|
|
97
|
+
|
|
98
|
+
Useful for batch operations where you want all errors, not just the first.
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
>>> collector = ExceptionCollector()
|
|
102
|
+
>>> with collector:
|
|
103
|
+
... collector.catch(int, "bad1")
|
|
104
|
+
... collector.catch(int, "bad2")
|
|
105
|
+
>>> if collector.has_errors:
|
|
106
|
+
... collector.raise_all()
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
__slots__ = ("_exceptions", "_stop_on_first")
|
|
110
|
+
|
|
111
|
+
def __init__(self, stop_on_first: bool = False) -> None:
|
|
112
|
+
self._exceptions: list[BaseException] = []
|
|
113
|
+
self._stop_on_first = stop_on_first
|
|
114
|
+
|
|
115
|
+
def __enter__(self) -> ExceptionCollector:
|
|
116
|
+
return self
|
|
117
|
+
|
|
118
|
+
def __exit__(self, exc_typ, exc_val, *_) -> bool:
|
|
119
|
+
if exc_typ is not None:
|
|
120
|
+
self._exceptions.append(exc_val)
|
|
121
|
+
if self._stop_on_first:
|
|
122
|
+
return False
|
|
123
|
+
return True
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
def catch(self, func, *args, **kwargs) -> bool:
|
|
127
|
+
"""Try to call func and catch any exception. Returns True if exception caught."""
|
|
128
|
+
try:
|
|
129
|
+
func(*args, **kwargs)
|
|
130
|
+
return False
|
|
131
|
+
except BaseException as exc:
|
|
132
|
+
self._exceptions.append(exc)
|
|
133
|
+
if self._stop_on_first:
|
|
134
|
+
raise
|
|
135
|
+
return True
|
|
136
|
+
|
|
137
|
+
def add(self, exc: BaseException) -> None:
|
|
138
|
+
"""Manually add an exception."""
|
|
139
|
+
self._exceptions.append(exc)
|
|
140
|
+
if self._stop_on_first:
|
|
141
|
+
raise exc
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def has_errors(self) -> bool:
|
|
145
|
+
"""Check if any exceptions were collected."""
|
|
146
|
+
return len(self._exceptions) > 0
|
|
147
|
+
|
|
148
|
+
@property
|
|
149
|
+
def count(self) -> int:
|
|
150
|
+
"""Get number of collected exceptions."""
|
|
151
|
+
return len(self._exceptions)
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def exceptions(self) -> list[BaseException]:
|
|
155
|
+
"""Get the list of exceptions."""
|
|
156
|
+
return self._exceptions
|
|
157
|
+
|
|
158
|
+
def raise_all(self, message: str = "collected errors") -> None:
|
|
159
|
+
"""Raise all collected exceptions as ExceptionGroup."""
|
|
160
|
+
if self._exceptions:
|
|
161
|
+
raise ExceptionGroup(message, cast(list[Exception], self._exceptions))
|
|
162
|
+
|
|
163
|
+
def clear(self) -> None:
|
|
164
|
+
"""Clear all exceptions."""
|
|
165
|
+
self._exceptions.clear()
|
|
@@ -48,8 +48,8 @@ def _format_record(record: Record, colorize: bool, fmt: str | None) -> str:
|
|
|
48
48
|
)
|
|
49
49
|
else:
|
|
50
50
|
# Default format (loguru-inspired)
|
|
51
|
-
# 2024-01-01 12:00:00.000 | INFO | module 10 - message
|
|
52
|
-
level_tag = f"{lv.name:<8}"
|
|
51
|
+
# 2024-01-01 12:00:00.000 | ℹ INFO | module 10 - message
|
|
52
|
+
level_tag = f"{lv.icon} {lv.name:<8}"
|
|
53
53
|
location = f"{Path(record.file).stem}:{record.function}:{record.line}"
|
|
54
54
|
|
|
55
55
|
if colorize:
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Partial function presets for errortools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from functools import partial
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
from .ignore import (
|
|
9
|
+
ignore,
|
|
10
|
+
ignore_subclass,
|
|
11
|
+
ignore_warns,
|
|
12
|
+
fast_ignore,
|
|
13
|
+
timeout,
|
|
14
|
+
retry,
|
|
15
|
+
)
|
|
16
|
+
from .decorator.cache import error_cache
|
|
17
|
+
from .const import (
|
|
18
|
+
LARGE_ERROR_CACHE_SIZE,
|
|
19
|
+
SMALL_ERROR_CACHE_SIZE,
|
|
20
|
+
DEFAULT_ERROR_CACHE_SIZE,
|
|
21
|
+
UNLIMITED_ERROR_CACHE,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# ------------------------------------------------------------------
|
|
25
|
+
# ignore: Common exception shortcuts
|
|
26
|
+
# ------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
ignoreTypeError: Callable = partial(ignore, TypeError)
|
|
29
|
+
ignoreValueError: Callable = partial(ignore, ValueError)
|
|
30
|
+
ignoreIndexError: Callable = partial(ignore, IndexError)
|
|
31
|
+
ignoreKeyError: Callable = partial(ignore, KeyError)
|
|
32
|
+
ignoreAttributeError: Callable = partial(ignore, AttributeError)
|
|
33
|
+
ignoreNameError: Callable = partial(ignore, NameError)
|
|
34
|
+
ignoreZeroDivisionError: Callable = partial(ignore, ZeroDivisionError)
|
|
35
|
+
ignoreFileNotFoundError: Callable = partial(ignore, FileNotFoundError)
|
|
36
|
+
ignorePermissionError: Callable = partial(ignore, PermissionError)
|
|
37
|
+
ignoreOSError: Callable = partial(ignore, OSError)
|
|
38
|
+
ignoreIOError: Callable = partial(ignore, IOError)
|
|
39
|
+
ignoreRuntimeError: Callable = partial(ignore, RuntimeError)
|
|
40
|
+
ignoreNotImplementedError: Callable = partial(ignore, NotImplementedError)
|
|
41
|
+
ignoreOverflowError: Callable = partial(ignore, OverflowError)
|
|
42
|
+
ignoreTimeoutError: Callable = partial(ignore, TimeoutError)
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------------
|
|
45
|
+
# fast_ignore
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
fast_ignoreTypeError: Callable = partial(fast_ignore, TypeError)
|
|
49
|
+
fast_ignoreValueError: Callable = partial(fast_ignore, ValueError)
|
|
50
|
+
fast_ignoreIndexError: Callable = partial(fast_ignore, IndexError)
|
|
51
|
+
fast_ignoreKeyError: Callable = partial(fast_ignore, KeyError)
|
|
52
|
+
fast_ignoreAttributeError: Callable = partial(fast_ignore, AttributeError)
|
|
53
|
+
fast_ignoreNameError: Callable = partial(fast_ignore, NameError)
|
|
54
|
+
fast_ignoreZeroDivisionError: Callable = partial(fast_ignore, ZeroDivisionError)
|
|
55
|
+
fast_ignoreFileNotFoundError: Callable = partial(fast_ignore, FileNotFoundError)
|
|
56
|
+
fast_ignorePermissionError: Callable = partial(fast_ignore, PermissionError)
|
|
57
|
+
fast_ignoreOSError: Callable = partial(fast_ignore, OSError)
|
|
58
|
+
fast_ignoreIOError: Callable = partial(fast_ignore, IOError)
|
|
59
|
+
fast_ignoreRuntimeError: Callable = partial(fast_ignore, RuntimeError)
|
|
60
|
+
fast_ignoreNotImplementedError: Callable = partial(fast_ignore, NotImplementedError)
|
|
61
|
+
fast_ignoreOverflowError: Callable = partial(fast_ignore, OverflowError)
|
|
62
|
+
fast_ignoreTimeoutError: Callable = partial(fast_ignore, TimeoutError)
|
|
63
|
+
|
|
64
|
+
# ------------------------------------------------------------------
|
|
65
|
+
# ignore_subclass
|
|
66
|
+
# ------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
ignoreSubclassException: Callable = partial(ignore_subclass, Exception)
|
|
69
|
+
ignoreSubclassOSError: Callable = partial(ignore_subclass, OSError)
|
|
70
|
+
|
|
71
|
+
# ------------------------------------------------------------------
|
|
72
|
+
# ignore_warns: Warning presets
|
|
73
|
+
# ------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
ignoreUserWarning: Callable = partial(ignore_warns, UserWarning)
|
|
76
|
+
ignoreDeprecationWarning: Callable = partial(ignore_warns, DeprecationWarning)
|
|
77
|
+
ignorePendingDeprecationWarning: Callable = partial(
|
|
78
|
+
ignore_warns, PendingDeprecationWarning
|
|
79
|
+
)
|
|
80
|
+
ignoreRuntimeWarning: Callable = partial(ignore_warns, RuntimeWarning)
|
|
81
|
+
ignoreSyntaxWarning: Callable = partial(ignore_warns, SyntaxWarning)
|
|
82
|
+
ignoreFutureWarning: Callable = partial(ignore_warns, FutureWarning)
|
|
83
|
+
ignoreImportWarning: Callable = partial(ignore_warns, ImportWarning)
|
|
84
|
+
ignoreUnicodeWarning: Callable = partial(ignore_warns, UnicodeWarning)
|
|
85
|
+
ignoreBytesWarning: Callable = partial(ignore_warns, BytesWarning)
|
|
86
|
+
ignoreResourceWarning: Callable = partial(ignore_warns, ResourceWarning)
|
|
87
|
+
|
|
88
|
+
# ------------------------------------------------------------------
|
|
89
|
+
# timeout presets (seconds)
|
|
90
|
+
# ------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
timeout_1s: Callable = partial(timeout, 1)
|
|
93
|
+
timeout_2s: Callable = partial(timeout, 2)
|
|
94
|
+
timeout_3s: Callable = partial(timeout, 3)
|
|
95
|
+
timeout_5s: Callable = partial(timeout, 5)
|
|
96
|
+
timeout_10s: Callable = partial(timeout, 10)
|
|
97
|
+
timeout_30s: Callable = partial(timeout, 30)
|
|
98
|
+
|
|
99
|
+
# ------------------------------------------------------------------
|
|
100
|
+
# retry presets
|
|
101
|
+
# ------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
retry_1: Callable = partial(retry, times=1)
|
|
104
|
+
retry_2: Callable = partial(retry, times=2)
|
|
105
|
+
retry_3: Callable = partial(retry, times=3)
|
|
106
|
+
retry_5: Callable = partial(retry, times=5)
|
|
107
|
+
retry_10: Callable = partial(retry, times=10)
|
|
108
|
+
|
|
109
|
+
retry_1_delay_1s: Callable = partial(retry, times=1, delay=1)
|
|
110
|
+
retry_2_delay_1s: Callable = partial(retry, times=2, delay=1)
|
|
111
|
+
retry_3_delay_1s: Callable = partial(retry, times=3, delay=1)
|
|
112
|
+
retry_3_delay_2s: Callable = partial(retry, times=3, delay=2)
|
|
113
|
+
retry_5_delay_1s: Callable = partial(retry, times=5, delay=1)
|
|
114
|
+
|
|
115
|
+
# ------------------------------------------------------------------
|
|
116
|
+
# error cache presets
|
|
117
|
+
# ------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
unlimited_error_cache: Callable = partial(error_cache, maxsize=UNLIMITED_ERROR_CACHE)
|
|
120
|
+
lru_error_cache: Callable = partial(error_cache, maxsize=DEFAULT_ERROR_CACHE_SIZE)
|
|
121
|
+
small_error_cache: Callable = partial(error_cache, maxsize=SMALL_ERROR_CACHE_SIZE)
|
|
122
|
+
large_error_cache: Callable = partial(error_cache, maxsize=LARGE_ERROR_CACHE_SIZE)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ------------------------------------------------------------------
|
|
126
|
+
# Export all
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
__all__ = [
|
|
130
|
+
# ignore
|
|
131
|
+
"ignoreTypeError",
|
|
132
|
+
"ignoreValueError",
|
|
133
|
+
"ignoreIndexError",
|
|
134
|
+
"ignoreKeyError",
|
|
135
|
+
"ignoreAttributeError",
|
|
136
|
+
"ignoreNameError",
|
|
137
|
+
"ignoreZeroDivisionError",
|
|
138
|
+
"ignoreFileNotFoundError",
|
|
139
|
+
"ignorePermissionError",
|
|
140
|
+
"ignoreOSError",
|
|
141
|
+
"ignoreIOError",
|
|
142
|
+
"ignoreRuntimeError",
|
|
143
|
+
"ignoreNotImplementedError",
|
|
144
|
+
"ignoreOverflowError",
|
|
145
|
+
"ignoreTimeoutError",
|
|
146
|
+
# fast_ignore
|
|
147
|
+
"fast_ignoreTypeError",
|
|
148
|
+
"fast_ignoreValueError",
|
|
149
|
+
"fast_ignoreIndexError",
|
|
150
|
+
"fast_ignoreKeyError",
|
|
151
|
+
"fast_ignoreAttributeError",
|
|
152
|
+
"fast_ignoreNameError",
|
|
153
|
+
"fast_ignoreZeroDivisionError",
|
|
154
|
+
"fast_ignoreFileNotFoundError",
|
|
155
|
+
"fast_ignorePermissionError",
|
|
156
|
+
"fast_ignoreOSError",
|
|
157
|
+
"fast_ignoreIOError",
|
|
158
|
+
"fast_ignoreRuntimeError",
|
|
159
|
+
"fast_ignoreNotImplementedError",
|
|
160
|
+
"fast_ignoreOverflowError",
|
|
161
|
+
"fast_ignoreTimeoutError",
|
|
162
|
+
# ignore_subclass
|
|
163
|
+
"ignoreSubclassException",
|
|
164
|
+
"ignoreSubclassOSError",
|
|
165
|
+
# ignore_warns
|
|
166
|
+
"ignoreUserWarning",
|
|
167
|
+
"ignoreDeprecationWarning",
|
|
168
|
+
"ignorePendingDeprecationWarning",
|
|
169
|
+
"ignoreRuntimeWarning",
|
|
170
|
+
"ignoreSyntaxWarning",
|
|
171
|
+
"ignoreFutureWarning",
|
|
172
|
+
"ignoreImportWarning",
|
|
173
|
+
"ignoreUnicodeWarning",
|
|
174
|
+
"ignoreBytesWarning",
|
|
175
|
+
"ignoreResourceWarning",
|
|
176
|
+
# timeout
|
|
177
|
+
"timeout_1s",
|
|
178
|
+
"timeout_2s",
|
|
179
|
+
"timeout_3s",
|
|
180
|
+
"timeout_5s",
|
|
181
|
+
"timeout_10s",
|
|
182
|
+
"timeout_30s",
|
|
183
|
+
# retry
|
|
184
|
+
"retry_1",
|
|
185
|
+
"retry_2",
|
|
186
|
+
"retry_3",
|
|
187
|
+
"retry_5",
|
|
188
|
+
"retry_10",
|
|
189
|
+
"retry_1_delay_1s",
|
|
190
|
+
"retry_2_delay_1s",
|
|
191
|
+
"retry_3_delay_1s",
|
|
192
|
+
"retry_3_delay_2s",
|
|
193
|
+
"retry_5_delay_1s",
|
|
194
|
+
# cache
|
|
195
|
+
"unlimited_error_cache",
|
|
196
|
+
"lru_error_cache",
|
|
197
|
+
"small_error_cache",
|
|
198
|
+
"large_error_cache",
|
|
199
|
+
]
|