errortools 2.0.0__tar.gz → 2.1.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.0.0/errortools.egg-info → errortools-2.1.0}/PKG-INFO +1 -1
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/version.py +2 -2
- errortools-2.1.0/_errortools/wrappers/__init__.py +0 -0
- errortools-2.1.0/_errortools/wrappers/cache.py +95 -0
- errortools-2.1.0/_errortools/wrappers/ignore.py +115 -0
- {errortools-2.0.0 → errortools-2.1.0/errortools.egg-info}/PKG-INFO +1 -1
- {errortools-2.0.0 → errortools-2.1.0}/errortools.egg-info/SOURCES.txt +3 -0
- {errortools-2.0.0 → errortools-2.1.0}/setup.py +1 -1
- {errortools-2.0.0 → errortools-2.1.0}/AUTHORS.txt +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/LICENSE.txt +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/README.md +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/__init__.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/_cli.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/classes/__init__.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/classes/abc.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/classes/errorcodes.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/classes/group.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/classes/warn.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/cli.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/const.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/decorator/__init__.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/decorator/cache.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/decorator/deprecated.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/future.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/ignore.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/logging/__init__.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/logging/base.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/logging/level.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/logging/logger.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/logging/record.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/logging/sink.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/metadata.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/methods/__init__.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/methods/errorattr.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/methods/errordelattr.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/methods/errorhasattr.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/methods/errorsetattr.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/partial.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/py.typed +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/raises.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/_errortools/typing.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/errortools/__init__.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/errortools/__main__.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/errortools.egg-info/dependency_links.txt +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/errortools.egg-info/entry_points.txt +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/errortools.egg-info/requires.txt +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/errortools.egg-info/top_level.txt +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/setup.cfg +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/__init__.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/conftest.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/run_tests.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_abc.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_cache.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_const.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_decorator.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_descriptor.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_errorcodes.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_groups.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_ignore.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_logging.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_mixins.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_partials.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_raises.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_typing.py +0 -0
- {errortools-2.0.0 → errortools-2.1.0}/tests/test_warnings.py +0 -0
|
File without changes
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
from collections.abc import Hashable, Callable
|
|
3
|
+
from typing import Any, Generic, TypeVar, Optional, TypeAlias, NamedTuple
|
|
4
|
+
|
|
5
|
+
_T = TypeVar("_T", bound=Callable[..., Any])
|
|
6
|
+
_Key: TypeAlias = tuple[
|
|
7
|
+
str, tuple[Hashable, ...], tuple[tuple[Hashable, Hashable], ...]
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CacheInfo(NamedTuple):
|
|
12
|
+
"""Cache statistics, compatible with functools.lru_cache CacheInfo."""
|
|
13
|
+
|
|
14
|
+
hits: int
|
|
15
|
+
misses: int
|
|
16
|
+
maxsize: int | None
|
|
17
|
+
currsize: int
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ErrorCacheWrapper(Generic[_T]):
|
|
21
|
+
"""Wrapper class for error-cached functions."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, func: _T, maxsize: int | None = 128) -> None:
|
|
24
|
+
self.__wrapped__ = func # Required for inspect module compatibility
|
|
25
|
+
self._func_name = func.__name__
|
|
26
|
+
|
|
27
|
+
# Validate maxsize
|
|
28
|
+
if maxsize is not None and maxsize < 0:
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f"maxsize must be None or a non-negative integer, got {maxsize!r}"
|
|
31
|
+
)
|
|
32
|
+
self._maxsize = maxsize
|
|
33
|
+
self._cache: OrderedDict[_Key, Exception] = OrderedDict()
|
|
34
|
+
|
|
35
|
+
# Cache statistics
|
|
36
|
+
self._hits = 0
|
|
37
|
+
self._misses = 0
|
|
38
|
+
|
|
39
|
+
def __call__(self, *args: Hashable, **kwargs: Hashable) -> Any:
|
|
40
|
+
"""Execute the wrapped function and cache exceptions if raised (with LRU)."""
|
|
41
|
+
cache_key = self._make_key(args, kwargs)
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
result = self.__wrapped__(*args, **kwargs)
|
|
45
|
+
except Exception as exc:
|
|
46
|
+
if cache_key in self._cache:
|
|
47
|
+
self._hits += 1
|
|
48
|
+
else:
|
|
49
|
+
self._misses += 1
|
|
50
|
+
|
|
51
|
+
# Store in cache only if maxsize allows it (maxsize=0 means no caching)
|
|
52
|
+
if self._maxsize != 0:
|
|
53
|
+
self._cache[cache_key] = exc
|
|
54
|
+
# Evict least recently used if maxsize is exceeded
|
|
55
|
+
if self._maxsize is not None and len(self._cache) > self._maxsize:
|
|
56
|
+
self._cache.popitem(last=False) # FIFO = LRU for insert order
|
|
57
|
+
raise
|
|
58
|
+
else:
|
|
59
|
+
# Auto-clear cache for successful calls
|
|
60
|
+
self._cache.pop(cache_key, None)
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
def _make_key(
|
|
64
|
+
self, args: tuple[Hashable, ...], kwargs: dict[str, Hashable]
|
|
65
|
+
) -> _Key:
|
|
66
|
+
"""Generate a unique hashable key."""
|
|
67
|
+
sorted_kwargs = tuple(sorted(kwargs.items()))
|
|
68
|
+
return (self._func_name, args, sorted_kwargs)
|
|
69
|
+
|
|
70
|
+
# ---------------------- Cache control methods (like lru_cache) ----------------------
|
|
71
|
+
def get_cached_error(
|
|
72
|
+
self, *args: Hashable, **kwargs: Hashable
|
|
73
|
+
) -> Optional[Exception]:
|
|
74
|
+
"""Get the cached exception for the given arguments (if exists)."""
|
|
75
|
+
cache_key = self._make_key(args, kwargs)
|
|
76
|
+
if cache_key in self._cache:
|
|
77
|
+
self._hits += 1
|
|
78
|
+
self._cache.move_to_end(cache_key) # Update LRU order (most recent)
|
|
79
|
+
return self._cache[cache_key]
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def clear_cache(self) -> None:
|
|
83
|
+
"""Clear all cached exceptions and reset statistics."""
|
|
84
|
+
self._cache.clear()
|
|
85
|
+
self._hits = 0
|
|
86
|
+
self._misses = 0
|
|
87
|
+
|
|
88
|
+
def cache_info(self) -> CacheInfo:
|
|
89
|
+
"""Return cache statistics as a named tuple (compatible with lru_cache)."""
|
|
90
|
+
return CacheInfo(
|
|
91
|
+
hits=self._hits,
|
|
92
|
+
misses=self._misses,
|
|
93
|
+
maxsize=self._maxsize,
|
|
94
|
+
currsize=len(self._cache),
|
|
95
|
+
)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Any, Generic, TypeVar, Optional, TypeAlias
|
|
4
|
+
|
|
5
|
+
_T = TypeVar("_T", bound=Callable[..., Any])
|
|
6
|
+
_ExcType: TypeAlias = type[Exception]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class IgnoredError:
|
|
10
|
+
"""Information holder for ignored exceptions."""
|
|
11
|
+
|
|
12
|
+
__slots__ = (
|
|
13
|
+
"name",
|
|
14
|
+
"be_ignore",
|
|
15
|
+
"count",
|
|
16
|
+
"traceback",
|
|
17
|
+
"exception",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
self.name: Optional[str] = None
|
|
22
|
+
self.be_ignore: bool = False
|
|
23
|
+
self.count: int = 0
|
|
24
|
+
self.traceback: Optional[str] = None
|
|
25
|
+
self.exception: Optional[Exception] = None
|
|
26
|
+
|
|
27
|
+
def reset(self) -> None:
|
|
28
|
+
self.name = None
|
|
29
|
+
self.be_ignore = False
|
|
30
|
+
self.traceback = None
|
|
31
|
+
self.exception = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ErrorIgnoreWrapper(Generic[_T]):
|
|
35
|
+
"""Context manager & decorator to ignore specified exceptions with rich info.
|
|
36
|
+
|
|
37
|
+
Catches and suppresses the given exception types within a ``with`` block
|
|
38
|
+
or when used as a decorator, while recording detailed information about
|
|
39
|
+
any suppressed exception.
|
|
40
|
+
|
|
41
|
+
When used as a context manager, ``__enter__`` returns an ``IgnoredError``
|
|
42
|
+
instance that provides the following attributes after the block executes.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# Attributes:
|
|
46
|
+
# be_ignore (bool):
|
|
47
|
+
# ``True`` if an exception was suppressed during the block,
|
|
48
|
+
# ``False`` otherwise.
|
|
49
|
+
|
|
50
|
+
# name (str | None):
|
|
51
|
+
# The class name of the suppressed exception
|
|
52
|
+
# (e.g. ``'KeyError'``, ``'ValueError'``).
|
|
53
|
+
# ``None`` if no exception occurred.
|
|
54
|
+
|
|
55
|
+
# count (int):
|
|
56
|
+
# Number of exceptions suppressed in this context block.
|
|
57
|
+
# Typically 1 unless the context manager is reused.
|
|
58
|
+
|
|
59
|
+
# exception (Exception | None):
|
|
60
|
+
# The original exception instance that was caught and suppressed.
|
|
61
|
+
# ``None`` if no exception occurred.
|
|
62
|
+
|
|
63
|
+
# traceback (str | None):
|
|
64
|
+
# Formatted traceback string showing where the suppressed exception
|
|
65
|
+
# occurred. Useful for debugging. ``None`` if no exception occurred.
|
|
66
|
+
|
|
67
|
+
# Example:
|
|
68
|
+
# >>> from errortools import ignore
|
|
69
|
+
# >>> with ignore(KeyError) as err:
|
|
70
|
+
# ... _ = {}["missing"]
|
|
71
|
+
# >>> err.be_ignore
|
|
72
|
+
# True
|
|
73
|
+
# >>> err.name
|
|
74
|
+
# 'KeyError'
|
|
75
|
+
|
|
76
|
+
def __init__(self, *excs: _ExcType) -> None:
|
|
77
|
+
for exc in excs:
|
|
78
|
+
if not isinstance(exc, type) or not issubclass(exc, Exception):
|
|
79
|
+
raise TypeError(f"Expected Exception subclass, got {exc!r}")
|
|
80
|
+
|
|
81
|
+
self._excs = excs
|
|
82
|
+
self._info = IgnoredError()
|
|
83
|
+
|
|
84
|
+
def __enter__(self) -> IgnoredError:
|
|
85
|
+
self._info.reset()
|
|
86
|
+
return self._info
|
|
87
|
+
|
|
88
|
+
def __exit__(
|
|
89
|
+
self,
|
|
90
|
+
exc_type: Optional[_ExcType],
|
|
91
|
+
exc_val: Optional[Exception],
|
|
92
|
+
exc_tb: Optional[Any],
|
|
93
|
+
) -> bool:
|
|
94
|
+
if exc_type is None:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
if exc_type not in self._excs:
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
self._info.name = exc_type.__name__
|
|
101
|
+
self._info.be_ignore = True
|
|
102
|
+
self._info.count += 1
|
|
103
|
+
self._info.traceback = "".join(
|
|
104
|
+
traceback.format_exception(exc_type, exc_val, exc_tb)
|
|
105
|
+
)
|
|
106
|
+
self._info.exception = exc_val
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
def __call__(self, func: _T) -> _T:
|
|
110
|
+
def wrapped(*args: Any, **kwargs: Any) -> Any:
|
|
111
|
+
with self:
|
|
112
|
+
return func(*args, **kwargs)
|
|
113
|
+
|
|
114
|
+
wrapped.__wrapped__ = func # type: ignore
|
|
115
|
+
return wrapped # type: ignore
|
|
@@ -33,6 +33,9 @@ _errortools/methods/errorattr.py
|
|
|
33
33
|
_errortools/methods/errordelattr.py
|
|
34
34
|
_errortools/methods/errorhasattr.py
|
|
35
35
|
_errortools/methods/errorsetattr.py
|
|
36
|
+
_errortools/wrappers/__init__.py
|
|
37
|
+
_errortools/wrappers/cache.py
|
|
38
|
+
_errortools/wrappers/ignore.py
|
|
36
39
|
errortools/__init__.py
|
|
37
40
|
errortools/__main__.py
|
|
38
41
|
errortools.egg-info/PKG-INFO
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="errortools",
|
|
5
|
-
version="2.
|
|
5
|
+
version="2.1.0",
|
|
6
6
|
description="errortools - a toolset for working with Python exceptions and warnings and logging.",
|
|
7
7
|
long_description=open("README.md", encoding="utf-8").read(),
|
|
8
8
|
long_description_content_type="text/markdown",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|