errortools 1.2.0__tar.gz → 1.3.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-1.2.0/errortools.egg-info → errortools-1.3.0}/PKG-INFO +24 -9
- {errortools-1.2.0 → errortools-1.3.0}/README.md +23 -8
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/cache.py +82 -81
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/classes/abc.py +351 -351
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/classes/group.py +118 -118
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/cli.py +94 -85
- errortools-1.3.0/_errortools/const.py +10 -0
- errortools-1.3.0/_errortools/decorator/__init__.py +1 -0
- errortools-1.3.0/_errortools/decorator/deprecated.py +33 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/ignore.py +89 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/logging/base.py +2 -2
- errortools-1.3.0/_errortools/partial.py +110 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/raises.py +183 -166
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/version.py +7 -7
- {errortools-1.2.0 → errortools-1.3.0}/errortools/__init__.py +158 -152
- {errortools-1.2.0 → errortools-1.3.0/errortools.egg-info}/PKG-INFO +24 -9
- {errortools-1.2.0 → errortools-1.3.0}/errortools.egg-info/SOURCES.txt +6 -2
- {errortools-1.2.0 → errortools-1.3.0}/setup.py +34 -34
- errortools-1.3.0/tests/__init__.py +12 -0
- errortools-1.3.0/tests/run_tests.py +19 -0
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_abc.py +303 -314
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_cache.py +4 -0
- errortools-1.3.0/tests/test_decorator.py +84 -0
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_descriptor.py +3 -12
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_errorcodes.py +4 -1
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_groups.py +3 -3
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_ignore.py +102 -1
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_logging.py +3 -4
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_mixins.py +3 -5
- errortools-1.3.0/tests/test_partials.py +232 -0
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_raises.py +3 -20
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_typing.py +5 -8
- {errortools-1.2.0 → errortools-1.3.0}/tests/test_warnings.py +3 -0
- errortools-1.2.0/_errortools/tools/__init__.py +0 -1
- errortools-1.2.0/_errortools/tools/_warps.py +0 -27
- errortools-1.2.0/tests/__init__.py +0 -3
- errortools-1.2.0/tests/run_tests.py +0 -13
- {errortools-1.2.0 → errortools-1.3.0}/AUTHORS.txt +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/LICENSE.txt +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/__init__.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/classes/__init__.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/classes/errorcodes.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/classes/warn.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/future.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/logging/__init__.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/logging/level.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/logging/logger.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/logging/record.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/logging/sink.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/metadata.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/methods/__init__.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/methods/errorattr.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/methods/errordelattr.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/methods/errorhasattr.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/methods/errorsetattr.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/py.typed +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/_errortools/typing.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/errortools/__main__.py +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/errortools.egg-info/dependency_links.txt +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/errortools.egg-info/entry_points.txt +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/errortools.egg-info/top_level.txt +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/setup.cfg +0 -0
- {errortools-1.2.0 → errortools-1.3.0}/tests/conftest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: errortools
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -35,7 +35,7 @@ A lightweight Python exception handling utility library.
|
|
|
35
35
|
|
|
36
36
|
## Features
|
|
37
37
|
- **Raise Exceptions**: `raises()`, `raises_all()`, `reraise()` — batch raising and exception conversion
|
|
38
|
-
- **Catch & Suppress**: `ignore()`, `ignore_subclass()`, `ignore_warns()`, `fast_ignore()`, `super_fast_ignore()`, `timeout()` — graceful suppression of exceptions and warnings
|
|
38
|
+
- **Catch & Suppress**: `ignore()`, `ignore_subclass()`, `ignore_warns()`, `fast_ignore()`, `super_fast_ignore()`, `timeout()`, `retry()` — graceful suppression of exceptions and warnings, with automatic retry
|
|
39
39
|
- **Exception Caching**: `error_cache` — cache exceptions raised by functions (similar to `lru_cache`)
|
|
40
40
|
- **Custom Exceptions**: `PureBaseException`, `ContextException`, `BaseErrorCodes`, `BaseWarning` — structured exception classes with error codes, trace IDs, and context
|
|
41
41
|
- **Attribute Error Mixin**: Customize error behavior for attribute access, assignment, and deletion
|
|
@@ -54,7 +54,7 @@ pip install errortools
|
|
|
54
54
|
```python
|
|
55
55
|
import warnings
|
|
56
56
|
from errortools import (
|
|
57
|
-
ignore, fast_ignore, ignore_subclass, ignore_warns, timeout,
|
|
57
|
+
ignore, fast_ignore, ignore_subclass, ignore_warns, timeout, retry,
|
|
58
58
|
reraise, raises, raises_all, assert_raises,
|
|
59
59
|
error_cache,
|
|
60
60
|
PureBaseException, ContextException, BaseErrorCodes, BaseWarning,
|
|
@@ -103,14 +103,29 @@ async def fetch_data(url: str) -> bytes:
|
|
|
103
103
|
|
|
104
104
|
# asyncio.TimeoutError raised automatically if it exceeds 5 s
|
|
105
105
|
|
|
106
|
-
# ── 6.
|
|
106
|
+
# ── 6. retry ── automatically retry on failure ───────────────────────────────
|
|
107
|
+
@retry(times=3, on=ConnectionError, delay=1.0)
|
|
108
|
+
def connect(host: str) -> None:
|
|
109
|
+
... # retried up to 3 times on ConnectionError
|
|
110
|
+
|
|
111
|
+
# works with async functions too
|
|
112
|
+
@retry(times=5, on=TimeoutError, delay=0.5)
|
|
113
|
+
async def fetch(url: str) -> bytes:
|
|
114
|
+
...
|
|
115
|
+
|
|
116
|
+
# multiple exception types
|
|
117
|
+
@retry(times=2, on=(ValueError, KeyError))
|
|
118
|
+
def parse(data: dict) -> str:
|
|
119
|
+
return data["key"]
|
|
120
|
+
|
|
121
|
+
# ── 7. reraise ── convert exception types on the fly ─────────────────────────
|
|
107
122
|
with reraise(KeyError, ValueError):
|
|
108
123
|
raise KeyError("missing key") # → ValueError: 'missing key'
|
|
109
124
|
|
|
110
125
|
with reraise((KeyError, IndexError), RuntimeError):
|
|
111
126
|
_ = [][99] # → RuntimeError: list index out of range
|
|
112
127
|
|
|
113
|
-
# ──
|
|
128
|
+
# ── 8. raises / raises_all ── batch raise ────────────────────────────────────
|
|
114
129
|
raises([ValueError], ["bad input"]) # → ValueError: bad input
|
|
115
130
|
|
|
116
131
|
raises_all(
|
|
@@ -118,11 +133,11 @@ raises_all(
|
|
|
118
133
|
["bad input"],
|
|
119
134
|
) # → ExceptionGroup (2 sub-exceptions)
|
|
120
135
|
|
|
121
|
-
# ──
|
|
136
|
+
# ── 9. assert_raises ── assert a callable raises ─────────────────────────────
|
|
122
137
|
exc = assert_raises(int, [ValueError], "not-a-number")
|
|
123
138
|
print(exc) # invalid literal for int() with base 10: 'not-a-number'
|
|
124
139
|
|
|
125
|
-
# ──
|
|
140
|
+
# ── 10. error_cache ── cache exceptions by call arguments ─────────────────────
|
|
126
141
|
@error_cache(maxsize=64)
|
|
127
142
|
def load(user_id: int) -> dict:
|
|
128
143
|
if user_id < 0:
|
|
@@ -135,7 +150,7 @@ with ignore(ValueError):
|
|
|
135
150
|
print(load.cache_info()) # CacheInfo(hits=0, misses=1, maxsize=64, currsize=1)
|
|
136
151
|
load.clear_cache()
|
|
137
152
|
|
|
138
|
-
# ──
|
|
153
|
+
# ── 11. Custom exceptions — three layers ──────────────────────────────────────
|
|
139
154
|
|
|
140
155
|
# Layer 1: PureBaseException — code + detail only
|
|
141
156
|
class AppError(PureBaseException):
|
|
@@ -171,7 +186,7 @@ raise BaseErrorCodes.runtime_failure("crash") # RuntimeFailure [
|
|
|
171
186
|
raise BaseErrorCodes.timeout_failure() # TimeoutFailure [4002]
|
|
172
187
|
raise BaseErrorCodes.configuration_error("missing key") # ConfigurationError [5001]
|
|
173
188
|
|
|
174
|
-
# ──
|
|
189
|
+
# ── 12. BaseWarning ── structured warnings with factory methods ───────────────
|
|
175
190
|
class ExperimentalWarning(BaseWarning):
|
|
176
191
|
default_detail = "This feature is experimental."
|
|
177
192
|
|
|
@@ -3,7 +3,7 @@ A lightweight Python exception handling utility library.
|
|
|
3
3
|
|
|
4
4
|
## Features
|
|
5
5
|
- **Raise Exceptions**: `raises()`, `raises_all()`, `reraise()` — batch raising and exception conversion
|
|
6
|
-
- **Catch & Suppress**: `ignore()`, `ignore_subclass()`, `ignore_warns()`, `fast_ignore()`, `super_fast_ignore()`, `timeout()` — graceful suppression of exceptions and warnings
|
|
6
|
+
- **Catch & Suppress**: `ignore()`, `ignore_subclass()`, `ignore_warns()`, `fast_ignore()`, `super_fast_ignore()`, `timeout()`, `retry()` — graceful suppression of exceptions and warnings, with automatic retry
|
|
7
7
|
- **Exception Caching**: `error_cache` — cache exceptions raised by functions (similar to `lru_cache`)
|
|
8
8
|
- **Custom Exceptions**: `PureBaseException`, `ContextException`, `BaseErrorCodes`, `BaseWarning` — structured exception classes with error codes, trace IDs, and context
|
|
9
9
|
- **Attribute Error Mixin**: Customize error behavior for attribute access, assignment, and deletion
|
|
@@ -22,7 +22,7 @@ pip install errortools
|
|
|
22
22
|
```python
|
|
23
23
|
import warnings
|
|
24
24
|
from errortools import (
|
|
25
|
-
ignore, fast_ignore, ignore_subclass, ignore_warns, timeout,
|
|
25
|
+
ignore, fast_ignore, ignore_subclass, ignore_warns, timeout, retry,
|
|
26
26
|
reraise, raises, raises_all, assert_raises,
|
|
27
27
|
error_cache,
|
|
28
28
|
PureBaseException, ContextException, BaseErrorCodes, BaseWarning,
|
|
@@ -71,14 +71,29 @@ async def fetch_data(url: str) -> bytes:
|
|
|
71
71
|
|
|
72
72
|
# asyncio.TimeoutError raised automatically if it exceeds 5 s
|
|
73
73
|
|
|
74
|
-
# ── 6.
|
|
74
|
+
# ── 6. retry ── automatically retry on failure ───────────────────────────────
|
|
75
|
+
@retry(times=3, on=ConnectionError, delay=1.0)
|
|
76
|
+
def connect(host: str) -> None:
|
|
77
|
+
... # retried up to 3 times on ConnectionError
|
|
78
|
+
|
|
79
|
+
# works with async functions too
|
|
80
|
+
@retry(times=5, on=TimeoutError, delay=0.5)
|
|
81
|
+
async def fetch(url: str) -> bytes:
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
# multiple exception types
|
|
85
|
+
@retry(times=2, on=(ValueError, KeyError))
|
|
86
|
+
def parse(data: dict) -> str:
|
|
87
|
+
return data["key"]
|
|
88
|
+
|
|
89
|
+
# ── 7. reraise ── convert exception types on the fly ─────────────────────────
|
|
75
90
|
with reraise(KeyError, ValueError):
|
|
76
91
|
raise KeyError("missing key") # → ValueError: 'missing key'
|
|
77
92
|
|
|
78
93
|
with reraise((KeyError, IndexError), RuntimeError):
|
|
79
94
|
_ = [][99] # → RuntimeError: list index out of range
|
|
80
95
|
|
|
81
|
-
# ──
|
|
96
|
+
# ── 8. raises / raises_all ── batch raise ────────────────────────────────────
|
|
82
97
|
raises([ValueError], ["bad input"]) # → ValueError: bad input
|
|
83
98
|
|
|
84
99
|
raises_all(
|
|
@@ -86,11 +101,11 @@ raises_all(
|
|
|
86
101
|
["bad input"],
|
|
87
102
|
) # → ExceptionGroup (2 sub-exceptions)
|
|
88
103
|
|
|
89
|
-
# ──
|
|
104
|
+
# ── 9. assert_raises ── assert a callable raises ─────────────────────────────
|
|
90
105
|
exc = assert_raises(int, [ValueError], "not-a-number")
|
|
91
106
|
print(exc) # invalid literal for int() with base 10: 'not-a-number'
|
|
92
107
|
|
|
93
|
-
# ──
|
|
108
|
+
# ── 10. error_cache ── cache exceptions by call arguments ─────────────────────
|
|
94
109
|
@error_cache(maxsize=64)
|
|
95
110
|
def load(user_id: int) -> dict:
|
|
96
111
|
if user_id < 0:
|
|
@@ -103,7 +118,7 @@ with ignore(ValueError):
|
|
|
103
118
|
print(load.cache_info()) # CacheInfo(hits=0, misses=1, maxsize=64, currsize=1)
|
|
104
119
|
load.clear_cache()
|
|
105
120
|
|
|
106
|
-
# ──
|
|
121
|
+
# ── 11. Custom exceptions — three layers ──────────────────────────────────────
|
|
107
122
|
|
|
108
123
|
# Layer 1: PureBaseException — code + detail only
|
|
109
124
|
class AppError(PureBaseException):
|
|
@@ -139,7 +154,7 @@ raise BaseErrorCodes.runtime_failure("crash") # RuntimeFailure [
|
|
|
139
154
|
raise BaseErrorCodes.timeout_failure() # TimeoutFailure [4002]
|
|
140
155
|
raise BaseErrorCodes.configuration_error("missing key") # ConfigurationError [5001]
|
|
141
156
|
|
|
142
|
-
# ──
|
|
157
|
+
# ── 12. BaseWarning ── structured warnings with factory methods ───────────────
|
|
143
158
|
class ExperimentalWarning(BaseWarning):
|
|
144
159
|
default_detail = "This feature is experimental."
|
|
145
160
|
|
|
@@ -1,81 +1,82 @@
|
|
|
1
|
-
"""A helper tool for caching exceptions raised by functions."""
|
|
2
|
-
|
|
3
|
-
import functools
|
|
4
|
-
from typing import (
|
|
5
|
-
Callable,
|
|
6
|
-
Any,
|
|
7
|
-
Optional,
|
|
8
|
-
TypeVar,
|
|
9
|
-
overload,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
from .
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
#
|
|
67
|
-
# -
|
|
68
|
-
# -
|
|
69
|
-
# -
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
1
|
+
"""A helper tool for caching exceptions raised by functions."""
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
from typing import (
|
|
5
|
+
Callable,
|
|
6
|
+
Any,
|
|
7
|
+
Optional,
|
|
8
|
+
TypeVar,
|
|
9
|
+
overload,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from .const import DEFAULT_ERROR_CACHE_SIZE
|
|
13
|
+
from .wrappers.cache import ErrorCacheWrapper
|
|
14
|
+
|
|
15
|
+
_T = TypeVar("_T", bound=Callable[..., Any])
|
|
16
|
+
|
|
17
|
+
# fmt: off
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@overload
|
|
21
|
+
def error_cache(func: _T) -> ErrorCacheWrapper[_T]:
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@overload
|
|
26
|
+
def error_cache(maxsize: Optional[int] = 128) -> Callable[[_T], ErrorCacheWrapper[_T]]:
|
|
27
|
+
...
|
|
28
|
+
# fmt: on
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def error_cache( # type: ignore
|
|
32
|
+
func: Optional[_T] = None, maxsize: Optional[int] = DEFAULT_ERROR_CACHE_SIZE
|
|
33
|
+
) -> Any:
|
|
34
|
+
"""
|
|
35
|
+
Decorator to cache exceptions raised by a function.
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
|
|
39
|
+
@error_cache # Default maxsize=128
|
|
40
|
+
def risky_func(x: int) -> int: ...
|
|
41
|
+
|
|
42
|
+
@error_cache(maxsize=32) # Custom maxsize
|
|
43
|
+
def risky_func(x: int) -> int: ...
|
|
44
|
+
|
|
45
|
+
@error_cache(maxsize=None) # Unlimited cache
|
|
46
|
+
def risky_func(x: int) -> int: ...
|
|
47
|
+
|
|
48
|
+
@error_cache() # Explicit empty args (maxsize=128)
|
|
49
|
+
def risky_func(x: int) -> int: ...
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
func: The function to wrap (auto-passed when using @error_cache without args).
|
|
53
|
+
maxsize: Maximum number of cached errors (None = unlimited, default=128).
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Wrapped function with error caching functionality.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
TypeError: If non-hashable arguments are passed.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# NOTE: This decorator automatically caches exceptions thrown by the wrapped function,
|
|
63
|
+
# keyed by the function's arguments. If the function succeeds, the cached exception
|
|
64
|
+
# (if any) for those arguments is removed.
|
|
65
|
+
|
|
66
|
+
# Key features:
|
|
67
|
+
# - maxsize: Maximum number of cached errors (None = unlimited, default=128)
|
|
68
|
+
# - LRU eviction: Evicts least recently used entries when maxsize is reached
|
|
69
|
+
# - cache_info(): Returns hits/misses/maxsize/currsize stats
|
|
70
|
+
# - clear_cache(): Clears cache and resets statistics
|
|
71
|
+
def decorator(f: _T) -> ErrorCacheWrapper[_T]:
|
|
72
|
+
if not callable(f):
|
|
73
|
+
raise TypeError(f"Expected a callable, got {type(f).__name__} instead")
|
|
74
|
+
|
|
75
|
+
wrapper = ErrorCacheWrapper(f, maxsize=maxsize)
|
|
76
|
+
functools.update_wrapper(wrapper, f)
|
|
77
|
+
return wrapper
|
|
78
|
+
|
|
79
|
+
# Handle both @error_cache and @error_cache(...) usage
|
|
80
|
+
if func is None:
|
|
81
|
+
return decorator
|
|
82
|
+
return decorator(func)
|