errortools 2.0.0__tar.gz → 2.2.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.
Files changed (68) hide show
  1. {errortools-2.0.0/errortools.egg-info → errortools-2.2.0}/PKG-INFO +1 -1
  2. errortools-2.2.0/_errortools/descriptor/__init__.py +2 -0
  3. errortools-2.2.0/_errortools/descriptor/errormsg.py +37 -0
  4. errortools-2.2.0/_errortools/descriptor/nonblankmsg.py +52 -0
  5. {errortools-2.0.0 → errortools-2.2.0}/_errortools/version.py +2 -2
  6. errortools-2.2.0/_errortools/wrappers/__init__.py +2 -0
  7. errortools-2.2.0/_errortools/wrappers/cache.py +95 -0
  8. errortools-2.2.0/_errortools/wrappers/ignore.py +115 -0
  9. {errortools-2.0.0 → errortools-2.2.0/errortools.egg-info}/PKG-INFO +1 -1
  10. {errortools-2.0.0 → errortools-2.2.0}/errortools.egg-info/SOURCES.txt +6 -0
  11. {errortools-2.0.0 → errortools-2.2.0}/setup.py +1 -1
  12. {errortools-2.0.0 → errortools-2.2.0}/AUTHORS.txt +0 -0
  13. {errortools-2.0.0 → errortools-2.2.0}/LICENSE.txt +0 -0
  14. {errortools-2.0.0 → errortools-2.2.0}/README.md +0 -0
  15. {errortools-2.0.0 → errortools-2.2.0}/_errortools/__init__.py +0 -0
  16. {errortools-2.0.0 → errortools-2.2.0}/_errortools/_cli.py +0 -0
  17. {errortools-2.0.0 → errortools-2.2.0}/_errortools/classes/__init__.py +0 -0
  18. {errortools-2.0.0 → errortools-2.2.0}/_errortools/classes/abc.py +0 -0
  19. {errortools-2.0.0 → errortools-2.2.0}/_errortools/classes/errorcodes.py +0 -0
  20. {errortools-2.0.0 → errortools-2.2.0}/_errortools/classes/group.py +0 -0
  21. {errortools-2.0.0 → errortools-2.2.0}/_errortools/classes/warn.py +0 -0
  22. {errortools-2.0.0 → errortools-2.2.0}/_errortools/cli.py +0 -0
  23. {errortools-2.0.0 → errortools-2.2.0}/_errortools/const.py +0 -0
  24. {errortools-2.0.0 → errortools-2.2.0}/_errortools/decorator/__init__.py +0 -0
  25. {errortools-2.0.0 → errortools-2.2.0}/_errortools/decorator/cache.py +0 -0
  26. {errortools-2.0.0 → errortools-2.2.0}/_errortools/decorator/deprecated.py +0 -0
  27. {errortools-2.0.0 → errortools-2.2.0}/_errortools/future.py +0 -0
  28. {errortools-2.0.0 → errortools-2.2.0}/_errortools/ignore.py +0 -0
  29. {errortools-2.0.0 → errortools-2.2.0}/_errortools/logging/__init__.py +0 -0
  30. {errortools-2.0.0 → errortools-2.2.0}/_errortools/logging/base.py +0 -0
  31. {errortools-2.0.0 → errortools-2.2.0}/_errortools/logging/level.py +0 -0
  32. {errortools-2.0.0 → errortools-2.2.0}/_errortools/logging/logger.py +0 -0
  33. {errortools-2.0.0 → errortools-2.2.0}/_errortools/logging/record.py +0 -0
  34. {errortools-2.0.0 → errortools-2.2.0}/_errortools/logging/sink.py +0 -0
  35. {errortools-2.0.0 → errortools-2.2.0}/_errortools/metadata.py +0 -0
  36. {errortools-2.0.0 → errortools-2.2.0}/_errortools/methods/__init__.py +0 -0
  37. {errortools-2.0.0 → errortools-2.2.0}/_errortools/methods/errorattr.py +0 -0
  38. {errortools-2.0.0 → errortools-2.2.0}/_errortools/methods/errordelattr.py +0 -0
  39. {errortools-2.0.0 → errortools-2.2.0}/_errortools/methods/errorhasattr.py +0 -0
  40. {errortools-2.0.0 → errortools-2.2.0}/_errortools/methods/errorsetattr.py +0 -0
  41. {errortools-2.0.0 → errortools-2.2.0}/_errortools/partial.py +0 -0
  42. {errortools-2.0.0 → errortools-2.2.0}/_errortools/py.typed +0 -0
  43. {errortools-2.0.0 → errortools-2.2.0}/_errortools/raises.py +0 -0
  44. {errortools-2.0.0 → errortools-2.2.0}/_errortools/typing.py +0 -0
  45. {errortools-2.0.0 → errortools-2.2.0}/errortools/__init__.py +0 -0
  46. {errortools-2.0.0 → errortools-2.2.0}/errortools/__main__.py +0 -0
  47. {errortools-2.0.0 → errortools-2.2.0}/errortools.egg-info/dependency_links.txt +0 -0
  48. {errortools-2.0.0 → errortools-2.2.0}/errortools.egg-info/entry_points.txt +0 -0
  49. {errortools-2.0.0 → errortools-2.2.0}/errortools.egg-info/requires.txt +0 -0
  50. {errortools-2.0.0 → errortools-2.2.0}/errortools.egg-info/top_level.txt +0 -0
  51. {errortools-2.0.0 → errortools-2.2.0}/setup.cfg +0 -0
  52. {errortools-2.0.0 → errortools-2.2.0}/tests/__init__.py +0 -0
  53. {errortools-2.0.0 → errortools-2.2.0}/tests/conftest.py +0 -0
  54. {errortools-2.0.0 → errortools-2.2.0}/tests/run_tests.py +0 -0
  55. {errortools-2.0.0 → errortools-2.2.0}/tests/test_abc.py +0 -0
  56. {errortools-2.0.0 → errortools-2.2.0}/tests/test_cache.py +0 -0
  57. {errortools-2.0.0 → errortools-2.2.0}/tests/test_const.py +0 -0
  58. {errortools-2.0.0 → errortools-2.2.0}/tests/test_decorator.py +0 -0
  59. {errortools-2.0.0 → errortools-2.2.0}/tests/test_descriptor.py +0 -0
  60. {errortools-2.0.0 → errortools-2.2.0}/tests/test_errorcodes.py +0 -0
  61. {errortools-2.0.0 → errortools-2.2.0}/tests/test_groups.py +0 -0
  62. {errortools-2.0.0 → errortools-2.2.0}/tests/test_ignore.py +0 -0
  63. {errortools-2.0.0 → errortools-2.2.0}/tests/test_logging.py +0 -0
  64. {errortools-2.0.0 → errortools-2.2.0}/tests/test_mixins.py +0 -0
  65. {errortools-2.0.0 → errortools-2.2.0}/tests/test_partials.py +0 -0
  66. {errortools-2.0.0 → errortools-2.2.0}/tests/test_raises.py +0 -0
  67. {errortools-2.0.0 → errortools-2.2.0}/tests/test_typing.py +0 -0
  68. {errortools-2.0.0 → errortools-2.2.0}/tests/test_warnings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: errortools
3
- Version: 2.0.0
3
+ Version: 2.2.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
@@ -0,0 +1,2 @@
1
+ # Why add this file?
2
+ # See https://github.com/more-abc/errortools/issues/27
@@ -0,0 +1,37 @@
1
+ from typing import Optional, NoReturn
2
+
3
+
4
+ # NOTE: The attribute returns the preset message when accessed.
5
+ # Any attempt to modify or delete it raises an ``AttributeError``.
6
+ class ErrorMsg:
7
+ """Descriptor that creates a read-only attribute with a fixed message.
8
+
9
+ Args:
10
+ message: The fixed string returned when the attribute is accessed.
11
+
12
+ Example:
13
+
14
+ >>> class MyClass:
15
+ ... status = ErrorMsg("This attribute is read-only")
16
+ >>> obj = MyClass()
17
+ >>> obj.status
18
+ 'This attribute is read-only'
19
+ >>> obj.status = "new" # doctest: +SKIP
20
+ AttributeError: Modification of this attribute is not allowed!
21
+ >>> del obj.status # doctest: +SKIP
22
+ AttributeError: Deletion of this attribute is not allowed!
23
+ """
24
+
25
+ __slots__ = ("_message",)
26
+
27
+ def __init__(self, message: str) -> None:
28
+ self._message = message
29
+
30
+ def __get__(self, instance: Optional[object], owner: type[object]) -> str:
31
+ return self._message
32
+
33
+ def __set__(self, instance: object, value: object) -> NoReturn:
34
+ raise AttributeError("Modification of this attribute is not allowed!")
35
+
36
+ def __delete__(self, instance: object) -> NoReturn:
37
+ raise AttributeError("Deletion of this attribute is not allowed!")
@@ -0,0 +1,52 @@
1
+ from typing import Any, Optional
2
+
3
+
4
+ # NOTE: Validates the value is a string and not empty
5
+ # after trimming leading/trailing whitespace.
6
+ # Stores the cleaned (stripped) value on the instance.
7
+ class NonBlankErrorMsg:
8
+ """Descriptor that validates an attribute is a non-blank string after stripping whitespace.
9
+
10
+ Args:
11
+ message: Name/label used in error messages for validation failures.
12
+
13
+ Example:
14
+ >>> class ApiError:
15
+ ... message = NonBlankErrorMsg("Error message")
16
+ ... def __init__(self, msg: str):
17
+ ... self.message = msg
18
+ >>> err = ApiError("Invalid token")
19
+ >>> err.message
20
+ 'Invalid token'
21
+ >>> err = ApiError(" ") # doctest: +SKIP
22
+ ValueError: Error message can't be blank, must provide a valid error message
23
+ """
24
+
25
+ __slots__ = ("_message",)
26
+
27
+ def __init__(self, message: str) -> None:
28
+ self._message = message
29
+
30
+ def __get__(self, instance: Optional[object], owner: type[object]) -> str:
31
+ if instance is None:
32
+ return self._message
33
+ return instance.__dict__[self._message] # type: ignore
34
+
35
+ def __set__(self, instance: object, value: Any) -> None:
36
+ validated_value = self.validate(self._message, value)
37
+ instance.__dict__[self._message] = validated_value
38
+
39
+ def __delete__(self, instance: object) -> None:
40
+ raise AttributeError("Deletion of this attribute is not allowed!")
41
+
42
+ def validate(self, name: str, value: str) -> str:
43
+ if not isinstance(value, str):
44
+ raise ValueError(f"{name} must be a string type")
45
+
46
+ stripped_value = value.strip()
47
+ if not stripped_value:
48
+ raise ValueError(
49
+ f"{name} can't be blank, must provide a valid error message"
50
+ )
51
+
52
+ return stripped_value
@@ -1,5 +1,5 @@
1
- __version__: str = "2.0.0"
2
- __version_tuple__: tuple[int, int, int] = (2, 0, 0)
1
+ __version__: str = "2.2.0"
2
+ __version_tuple__: tuple[int, int, int] = (2, 2, 0)
3
3
  __commit_id__: str | None = None
4
4
 
5
5
  version = __version__
@@ -0,0 +1,2 @@
1
+ # Why add this file?
2
+ # See https://github.com/more-abc/errortools/issues/27
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: errortools
3
- Version: 2.0.0
3
+ Version: 2.2.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
@@ -22,6 +22,9 @@ _errortools/classes/warn.py
22
22
  _errortools/decorator/__init__.py
23
23
  _errortools/decorator/cache.py
24
24
  _errortools/decorator/deprecated.py
25
+ _errortools/descriptor/__init__.py
26
+ _errortools/descriptor/errormsg.py
27
+ _errortools/descriptor/nonblankmsg.py
25
28
  _errortools/logging/__init__.py
26
29
  _errortools/logging/base.py
27
30
  _errortools/logging/level.py
@@ -33,6 +36,9 @@ _errortools/methods/errorattr.py
33
36
  _errortools/methods/errordelattr.py
34
37
  _errortools/methods/errorhasattr.py
35
38
  _errortools/methods/errorsetattr.py
39
+ _errortools/wrappers/__init__.py
40
+ _errortools/wrappers/cache.py
41
+ _errortools/wrappers/ignore.py
36
42
  errortools/__init__.py
37
43
  errortools/__main__.py
38
44
  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.0.0",
5
+ version="2.2.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