errortools 1.3.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.
Files changed (67) hide show
  1. errortools-2.1.0/AUTHORS.txt +3 -0
  2. {errortools-1.3.0/errortools.egg-info → errortools-2.1.0}/PKG-INFO +19 -3
  3. {errortools-1.3.0 → errortools-2.1.0}/README.md +16 -2
  4. errortools-2.1.0/_errortools/_cli.py +32 -0
  5. {errortools-1.3.0 → errortools-2.1.0}/_errortools/cli.py +34 -8
  6. {errortools-1.3.0 → errortools-2.1.0}/_errortools/const.py +10 -10
  7. {errortools-1.3.0/_errortools → errortools-2.1.0/_errortools/decorator}/cache.py +2 -2
  8. {errortools-1.3.0 → errortools-2.1.0}/_errortools/ignore.py +50 -10
  9. {errortools-1.3.0 → errortools-2.1.0}/_errortools/logging/sink.py +1 -1
  10. {errortools-1.3.0 → errortools-2.1.0}/_errortools/metadata.py +7 -0
  11. {errortools-1.3.0 → errortools-2.1.0}/_errortools/partial.py +1 -1
  12. {errortools-1.3.0 → errortools-2.1.0}/_errortools/version.py +2 -2
  13. errortools-2.1.0/_errortools/wrappers/__init__.py +0 -0
  14. errortools-2.1.0/_errortools/wrappers/cache.py +95 -0
  15. errortools-2.1.0/_errortools/wrappers/ignore.py +115 -0
  16. {errortools-1.3.0 → errortools-2.1.0}/errortools/__init__.py +9 -1
  17. {errortools-1.3.0 → errortools-2.1.0/errortools.egg-info}/PKG-INFO +19 -3
  18. {errortools-1.3.0 → errortools-2.1.0}/errortools.egg-info/SOURCES.txt +7 -1
  19. errortools-2.1.0/errortools.egg-info/entry_points.txt +3 -0
  20. errortools-2.1.0/errortools.egg-info/requires.txt +1 -0
  21. {errortools-1.3.0 → errortools-2.1.0}/setup.py +4 -3
  22. {errortools-1.3.0 → errortools-2.1.0}/tests/__init__.py +12 -12
  23. {errortools-1.3.0 → errortools-2.1.0}/tests/test_abc.py +1 -0
  24. {errortools-1.3.0 → errortools-2.1.0}/tests/test_cache.py +2 -1
  25. errortools-2.1.0/tests/test_const.py +33 -0
  26. {errortools-1.3.0 → errortools-2.1.0}/tests/test_decorator.py +1 -0
  27. {errortools-1.3.0 → errortools-2.1.0}/tests/test_descriptor.py +2 -0
  28. {errortools-1.3.0 → errortools-2.1.0}/tests/test_errorcodes.py +2 -0
  29. {errortools-1.3.0 → errortools-2.1.0}/tests/test_groups.py +2 -0
  30. {errortools-1.3.0 → errortools-2.1.0}/tests/test_ignore.py +2 -0
  31. {errortools-1.3.0 → errortools-2.1.0}/tests/test_logging.py +2 -0
  32. {errortools-1.3.0 → errortools-2.1.0}/tests/test_mixins.py +2 -0
  33. {errortools-1.3.0 → errortools-2.1.0}/tests/test_partials.py +1 -0
  34. {errortools-1.3.0 → errortools-2.1.0}/tests/test_raises.py +1 -0
  35. {errortools-1.3.0 → errortools-2.1.0}/tests/test_typing.py +1 -0
  36. {errortools-1.3.0 → errortools-2.1.0}/tests/test_warnings.py +2 -0
  37. errortools-1.3.0/AUTHORS.txt +0 -2
  38. errortools-1.3.0/errortools.egg-info/entry_points.txt +0 -2
  39. {errortools-1.3.0 → errortools-2.1.0}/LICENSE.txt +0 -0
  40. {errortools-1.3.0 → errortools-2.1.0}/_errortools/__init__.py +0 -0
  41. {errortools-1.3.0 → errortools-2.1.0}/_errortools/classes/__init__.py +0 -0
  42. {errortools-1.3.0 → errortools-2.1.0}/_errortools/classes/abc.py +0 -0
  43. {errortools-1.3.0 → errortools-2.1.0}/_errortools/classes/errorcodes.py +0 -0
  44. {errortools-1.3.0 → errortools-2.1.0}/_errortools/classes/group.py +0 -0
  45. {errortools-1.3.0 → errortools-2.1.0}/_errortools/classes/warn.py +0 -0
  46. {errortools-1.3.0 → errortools-2.1.0}/_errortools/decorator/__init__.py +0 -0
  47. {errortools-1.3.0 → errortools-2.1.0}/_errortools/decorator/deprecated.py +0 -0
  48. {errortools-1.3.0 → errortools-2.1.0}/_errortools/future.py +0 -0
  49. {errortools-1.3.0 → errortools-2.1.0}/_errortools/logging/__init__.py +0 -0
  50. {errortools-1.3.0 → errortools-2.1.0}/_errortools/logging/base.py +0 -0
  51. {errortools-1.3.0 → errortools-2.1.0}/_errortools/logging/level.py +0 -0
  52. {errortools-1.3.0 → errortools-2.1.0}/_errortools/logging/logger.py +0 -0
  53. {errortools-1.3.0 → errortools-2.1.0}/_errortools/logging/record.py +0 -0
  54. {errortools-1.3.0 → errortools-2.1.0}/_errortools/methods/__init__.py +0 -0
  55. {errortools-1.3.0 → errortools-2.1.0}/_errortools/methods/errorattr.py +0 -0
  56. {errortools-1.3.0 → errortools-2.1.0}/_errortools/methods/errordelattr.py +0 -0
  57. {errortools-1.3.0 → errortools-2.1.0}/_errortools/methods/errorhasattr.py +0 -0
  58. {errortools-1.3.0 → errortools-2.1.0}/_errortools/methods/errorsetattr.py +0 -0
  59. {errortools-1.3.0 → errortools-2.1.0}/_errortools/py.typed +0 -0
  60. {errortools-1.3.0 → errortools-2.1.0}/_errortools/raises.py +0 -0
  61. {errortools-1.3.0 → errortools-2.1.0}/_errortools/typing.py +0 -0
  62. {errortools-1.3.0 → errortools-2.1.0}/errortools/__main__.py +0 -0
  63. {errortools-1.3.0 → errortools-2.1.0}/errortools.egg-info/dependency_links.txt +0 -0
  64. {errortools-1.3.0 → errortools-2.1.0}/errortools.egg-info/top_level.txt +0 -0
  65. {errortools-1.3.0 → errortools-2.1.0}/setup.cfg +0 -0
  66. {errortools-1.3.0 → errortools-2.1.0}/tests/conftest.py +0 -0
  67. {errortools-1.3.0 → errortools-2.1.0}/tests/run_tests.py +0 -0
@@ -0,0 +1,3 @@
1
+ aiwonderland
2
+ qorexdev
3
+ vgauraha62
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: errortools
3
- Version: 1.3.0
3
+ Version: 2.1.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
@@ -19,6 +19,7 @@ Requires-Python: >=3.10
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.txt
21
21
  License-File: AUTHORS.txt
22
+ Requires-Dist: namebyauthor==1.0.0
22
23
  Dynamic: author
23
24
  Dynamic: author-email
24
25
  Dynamic: classifier
@@ -27,6 +28,7 @@ Dynamic: description-content-type
27
28
  Dynamic: home-page
28
29
  Dynamic: license
29
30
  Dynamic: license-file
31
+ Dynamic: requires-dist
30
32
  Dynamic: requires-python
31
33
  Dynamic: summary
32
34
 
@@ -49,7 +51,7 @@ pip install errortools
49
51
 
50
52
  ---
51
53
 
52
- ## Example
54
+ ## Examples
53
55
 
54
56
  ```python
55
57
  import warnings
@@ -70,8 +72,22 @@ assert err.name == "KeyError" # exception class name
70
72
  assert err.count == 1 # how many times suppressed
71
73
  assert err.exception # the original KeyError instance
72
74
  assert err.traceback # full formatted traceback string
75
+ ```
76
+
77
+ **Attributes on the returned object:**
78
+
79
+ | Attribute | Type | Description |
80
+ |---|---|---|
81
+ | `be_ignore` | `bool` | `True` if an exception was suppressed |
82
+ | `name` | `str \| None` | Class name of the suppressed exception |
83
+ | `count` | `int` | Number of suppressed exceptions |
84
+ | `exception` | `Exception \| None` | The original exception instance |
85
+ | `traceback` | `str \| None` | Formatted traceback string for debugging |
86
+
87
+ ## Examples
73
88
 
74
- # ignore as a decorator
89
+ ```python
90
+ # ── ignore as a decorator ──
75
91
  @ignore(ValueError, TypeError)
76
92
  def parse(x: str) -> int:
77
93
  return int(x)
@@ -17,7 +17,7 @@ pip install errortools
17
17
 
18
18
  ---
19
19
 
20
- ## Example
20
+ ## Examples
21
21
 
22
22
  ```python
23
23
  import warnings
@@ -38,8 +38,22 @@ assert err.name == "KeyError" # exception class name
38
38
  assert err.count == 1 # how many times suppressed
39
39
  assert err.exception # the original KeyError instance
40
40
  assert err.traceback # full formatted traceback string
41
+ ```
42
+
43
+ **Attributes on the returned object:**
44
+
45
+ | Attribute | Type | Description |
46
+ |---|---|---|
47
+ | `be_ignore` | `bool` | `True` if an exception was suppressed |
48
+ | `name` | `str \| None` | Class name of the suppressed exception |
49
+ | `count` | `int` | Number of suppressed exceptions |
50
+ | `exception` | `Exception \| None` | The original exception instance |
51
+ | `traceback` | `str \| None` | Formatted traceback string for debugging |
52
+
53
+ ## Examples
41
54
 
42
- # ignore as a decorator
55
+ ```python
56
+ # ── ignore as a decorator ──
43
57
  @ignore(ValueError, TypeError)
44
58
  def parse(x: str) -> int:
45
59
  return int(x)
@@ -0,0 +1,32 @@
1
+ import sys
2
+
3
+ from _errortools.metadata import (
4
+ __author__,
5
+ __author_email__,
6
+ __copyright__,
7
+ __description__,
8
+ __license__,
9
+ __url__,
10
+ )
11
+ from _errortools.version import __version__
12
+
13
+
14
+ def _cmd_log(message: str, level: str, output: str) -> None:
15
+ """Emit a single log message via the errortools logger."""
16
+ from .logging import BaseLogger
17
+ from .logging.level import get_level
18
+
19
+ stream = sys.stdout if output == "stdout" else sys.stderr
20
+ log = BaseLogger(name="errortools-cli")
21
+ log.add(stream, level="TRACE", colorize=None)
22
+ log.log(get_level(level), message)
23
+
24
+
25
+ def _print_info() -> None:
26
+ """Print a summary of all package metadata."""
27
+ print(f"errortools v{__version__}")
28
+ print(f" {__description__}")
29
+ print(f" Author: {__author__} <{__author_email__}>")
30
+ print(f" License: {__license__}")
31
+ print(f" URL: {__url__}")
32
+ print(f" Copyright: {__copyright__}")
@@ -3,6 +3,8 @@
3
3
  import argparse
4
4
  import sys
5
5
 
6
+ from _errortools._cli import _cmd_log, _print_info
7
+
6
8
  from .metadata import (
7
9
  __description__,
8
10
  __copyright__,
@@ -49,17 +51,41 @@ def parse_args(args: list[str] | None = None) -> argparse.Namespace:
49
51
  action="store_true",
50
52
  help="Run tests for errortools module. (Using pytest)",
51
53
  )
54
+
55
+ subparsers = parser.add_subparsers(dest="subcommand")
56
+
57
+ log_parser = subparsers.add_parser(
58
+ "log",
59
+ help="Emit a log message from the command line",
60
+ )
61
+ log_parser.add_argument(
62
+ "message",
63
+ help="The message to log",
64
+ )
65
+ log_parser.add_argument(
66
+ "--level",
67
+ "-l",
68
+ default="info",
69
+ choices=["trace", "debug", "info", "success", "warning", "error", "critical"],
70
+ help="Log level (default: info)",
71
+ )
72
+ log_parser.add_argument(
73
+ "--output",
74
+ "-o",
75
+ choices=["stderr", "stdout"],
76
+ default="stderr",
77
+ help="Output stream (default: stderr)",
78
+ )
79
+
52
80
  return parser.parse_args(args)
53
81
 
54
82
 
55
- def _print_info() -> None:
56
- """Print a summary of all package metadata."""
57
- print(f"errortools v{__version__}")
58
- print(f" {__description__}")
59
- print(f" Author: {__author__} <{__author_email__}>")
60
- print(f" License: {__license__}")
61
- print(f" URL: {__url__}")
62
- print(f" Copyright: {__copyright__}")
83
+ def log_main() -> None:
84
+ """Logging main CLI entry point."""
85
+ args = parse_args(sys.argv[1:])
86
+
87
+ if args.subcommand == "log":
88
+ _cmd_log(args.message, args.level, args.output)
63
89
 
64
90
 
65
91
  def main() -> None:
@@ -1,10 +1,10 @@
1
- """Constants for errortools module."""
2
-
3
- # ------------------------------------------------------------------
4
- # error_cache constants
5
- # ------------------------------------------------------------------
6
-
7
- DEFAULT_ERROR_CACHE_SIZE: int = 128
8
- SMALL_ERROR_CACHE_SIZE: int = 64
9
- LARGE_ERROR_CACHE_SIZE: int = 1024
10
- UNLIMITED_ERROR_CACHE: None = None
1
+ """Constants for errortools module."""
2
+
3
+ # ------------------------------------------------------------------
4
+ # error_cache constants
5
+ # ------------------------------------------------------------------
6
+
7
+ DEFAULT_ERROR_CACHE_SIZE: int = 128
8
+ SMALL_ERROR_CACHE_SIZE: int = 64
9
+ LARGE_ERROR_CACHE_SIZE: int = 1024
10
+ UNLIMITED_ERROR_CACHE: None = None
@@ -9,8 +9,8 @@ from typing import (
9
9
  overload,
10
10
  )
11
11
 
12
- from .const import DEFAULT_ERROR_CACHE_SIZE
13
- from .wrappers.cache import ErrorCacheWrapper
12
+ from ..const import DEFAULT_ERROR_CACHE_SIZE
13
+ from ..wrappers.cache import ErrorCacheWrapper
14
14
 
15
15
  _T = TypeVar("_T", bound=Callable[..., Any])
16
16
 
@@ -33,20 +33,60 @@ __all__ = [
33
33
  # It captures the full exception traceback and metadata, which results in higher overhead.
34
34
  # Use it for error handling and diagnostics, not for tight loops.
35
35
  ignore = ErrorIgnoreWrapper
36
- """Context manager that silently suppresses the given exception types.
36
+ """Context manager that suppresses specified exceptions and captures metadata.
37
37
 
38
- It captures the full exception traceback and metadata, which results in higher overhead.
39
- Use it for error handling and diagnostics, not for tight loops. (Use `fast_ignore` faster)
38
+ Catches and suppresses the given exception types within a ``with`` block,
39
+ while recording detailed information about any suppressed exception.
40
40
 
41
41
  Args:
42
42
  *excs: One or more exception types to suppress.
43
43
 
44
- Example:
45
- >>> with ignore(KeyError) as error:
46
- ... d = {}
47
- ... _ = d["missing"]
48
- >>> print(error.be_ignore) # True
49
- """
44
+ Returns:
45
+ An ``IgnoredError`` instance (via ``as``) that provides access to
46
+ exception metadata after the block executes.
47
+
48
+ Attributes on the returned ``IgnoredError``:
49
+ be_ignore (bool):
50
+ ``True`` if an exception was suppressed during the block,
51
+ ``False`` otherwise.
52
+
53
+ name (str | None):
54
+ The class name of the suppressed exception
55
+ (e.g. ``'KeyError'``, ``'ValueError'``).
56
+ ``None`` if no exception occurred.
57
+
58
+ count (int):
59
+ Number of exceptions suppressed in this context block.
60
+ Typically 1 unless the context manager is reused.
61
+
62
+ exception (Exception | None):
63
+ The original exception instance that was caught and suppressed.
64
+ ``None`` if no exception occurred.
65
+
66
+ traceback (str | None):
67
+ Formatted traceback string showing where the suppressed exception
68
+ occurred. Useful for debugging. ``None`` if no exception occurred.
69
+
70
+ Examples:
71
+
72
+ >>> with ignore(KeyError, ValueError) as err:
73
+ ... data = {}["missing_key"]
74
+ ...
75
+ >>> print(err.be_ignore) # True
76
+ >>> print(err.name) # 'KeyError'
77
+ >>> print(err.count) # 1
78
+ >>> print(err.exception) # KeyError('missing_key')
79
+ >>> print(err.traceback) # Formatted traceback string
80
+
81
+ Note:
82
+ Use `fast_ignore` or `super_fast_ignore`(in `future` submodule) for zero-overhead
83
+ suppression when metadata collection is not needed.
84
+
85
+ See Also:
86
+ - `fast_ignore` — minimal overhead, no metadata
87
+ - `ignore_subclass` — suppress exceptions including subclasses
88
+ - `retry` — automatic retry on exception
89
+ """
50
90
 
51
91
 
52
92
  class fast_ignore:
@@ -59,7 +99,7 @@ class fast_ignore:
59
99
  Args:
60
100
  *excs: One or more exception types to suppress.
61
101
 
62
- Example:
102
+ Examples:
63
103
  >>> with fast_ignore(KeyError):
64
104
  ... d = {}
65
105
  ... _ = d["missing"]
@@ -48,7 +48,7 @@ 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:func:10 - message
51
+ # 2024-01-01 12:00:00.000 | INFO | module 10 - message
52
52
  level_tag = f"{lv.name:<8}"
53
53
  location = f"{Path(record.file).stem}:{record.function}:{record.line}"
54
54
 
@@ -1,3 +1,5 @@
1
+ import namebyauthor as na # type: ignore
2
+
1
3
  __author__: str = "Evan Yang"
2
4
  __author_email__: str = "quantbit@126.com"
3
5
  __copyright__: str = "Copyright 2026 (C) aiwonderland Evan Yang"
@@ -7,3 +9,8 @@ __description__: str = (
7
9
  __license__: str = "MIT"
8
10
  __title__: str = "errortools"
9
11
  __url__: str = "https://github.com/more-abc/errortools"
12
+
13
+ __fullname__ = na.generate_name(__title__, __author__)
14
+ __slug__ = na.generate_slug(__title__, __author__)
15
+ __signature__ = na.generate_signature(__title__, __author__)
16
+ __uid__ = na.generate_id(__title__, __author__)
@@ -3,7 +3,7 @@
3
3
  from functools import partial
4
4
 
5
5
  from .ignore import ignore, ignore_subclass, ignore_warns, fast_ignore, timeout, retry
6
- from .cache import error_cache
6
+ from .decorator.cache import error_cache
7
7
  from .const import (
8
8
  LARGE_ERROR_CACHE_SIZE,
9
9
  SMALL_ERROR_CACHE_SIZE,
@@ -1,5 +1,5 @@
1
- __version__: str = "1.3.0"
2
- __version_tuple__: tuple[int, int, int] = (1, 3, 0)
1
+ __version__: str = "2.1.0"
2
+ __version_tuple__: tuple[int, int, int] = (2, 1, 0)
3
3
  __commit_id__: str | None = None
4
4
 
5
5
  version = __version__
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
@@ -12,7 +12,7 @@ from _errortools.ignore import (
12
12
  retry,
13
13
  )
14
14
  from _errortools.classes.group import BaseGroup, GroupErrors
15
- from _errortools.cache import error_cache
15
+ from _errortools.decorator.cache import error_cache
16
16
  from _errortools.decorator.deprecated import deprecated
17
17
  from _errortools.classes.errorcodes import (
18
18
  PureBaseException,
@@ -77,6 +77,10 @@ from _errortools.metadata import (
77
77
  __license__,
78
78
  __title__,
79
79
  __url__,
80
+ __fullname__,
81
+ __signature__,
82
+ __slug__,
83
+ __uid__,
80
84
  )
81
85
  import _errortools.future as future
82
86
  import _errortools.logging as logging
@@ -151,6 +155,10 @@ __all__ = [
151
155
  "__license__",
152
156
  "__title__",
153
157
  "__url__",
158
+ "__fullname__",
159
+ "__signature__",
160
+ "__slug__",
161
+ "__uid__",
154
162
  # submodules
155
163
  "future",
156
164
  "logging",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: errortools
3
- Version: 1.3.0
3
+ Version: 2.1.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
@@ -19,6 +19,7 @@ Requires-Python: >=3.10
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.txt
21
21
  License-File: AUTHORS.txt
22
+ Requires-Dist: namebyauthor==1.0.0
22
23
  Dynamic: author
23
24
  Dynamic: author-email
24
25
  Dynamic: classifier
@@ -27,6 +28,7 @@ Dynamic: description-content-type
27
28
  Dynamic: home-page
28
29
  Dynamic: license
29
30
  Dynamic: license-file
31
+ Dynamic: requires-dist
30
32
  Dynamic: requires-python
31
33
  Dynamic: summary
32
34
 
@@ -49,7 +51,7 @@ pip install errortools
49
51
 
50
52
  ---
51
53
 
52
- ## Example
54
+ ## Examples
53
55
 
54
56
  ```python
55
57
  import warnings
@@ -70,8 +72,22 @@ assert err.name == "KeyError" # exception class name
70
72
  assert err.count == 1 # how many times suppressed
71
73
  assert err.exception # the original KeyError instance
72
74
  assert err.traceback # full formatted traceback string
75
+ ```
76
+
77
+ **Attributes on the returned object:**
78
+
79
+ | Attribute | Type | Description |
80
+ |---|---|---|
81
+ | `be_ignore` | `bool` | `True` if an exception was suppressed |
82
+ | `name` | `str \| None` | Class name of the suppressed exception |
83
+ | `count` | `int` | Number of suppressed exceptions |
84
+ | `exception` | `Exception \| None` | The original exception instance |
85
+ | `traceback` | `str \| None` | Formatted traceback string for debugging |
86
+
87
+ ## Examples
73
88
 
74
- # ignore as a decorator
89
+ ```python
90
+ # ── ignore as a decorator ──
75
91
  @ignore(ValueError, TypeError)
76
92
  def parse(x: str) -> int:
77
93
  return int(x)
@@ -3,7 +3,7 @@ LICENSE.txt
3
3
  README.md
4
4
  setup.py
5
5
  _errortools/__init__.py
6
- _errortools/cache.py
6
+ _errortools/_cli.py
7
7
  _errortools/cli.py
8
8
  _errortools/const.py
9
9
  _errortools/future.py
@@ -20,6 +20,7 @@ _errortools/classes/errorcodes.py
20
20
  _errortools/classes/group.py
21
21
  _errortools/classes/warn.py
22
22
  _errortools/decorator/__init__.py
23
+ _errortools/decorator/cache.py
23
24
  _errortools/decorator/deprecated.py
24
25
  _errortools/logging/__init__.py
25
26
  _errortools/logging/base.py
@@ -32,18 +33,23 @@ _errortools/methods/errorattr.py
32
33
  _errortools/methods/errordelattr.py
33
34
  _errortools/methods/errorhasattr.py
34
35
  _errortools/methods/errorsetattr.py
36
+ _errortools/wrappers/__init__.py
37
+ _errortools/wrappers/cache.py
38
+ _errortools/wrappers/ignore.py
35
39
  errortools/__init__.py
36
40
  errortools/__main__.py
37
41
  errortools.egg-info/PKG-INFO
38
42
  errortools.egg-info/SOURCES.txt
39
43
  errortools.egg-info/dependency_links.txt
40
44
  errortools.egg-info/entry_points.txt
45
+ errortools.egg-info/requires.txt
41
46
  errortools.egg-info/top_level.txt
42
47
  tests/__init__.py
43
48
  tests/conftest.py
44
49
  tests/run_tests.py
45
50
  tests/test_abc.py
46
51
  tests/test_cache.py
52
+ tests/test_const.py
47
53
  tests/test_decorator.py
48
54
  tests/test_descriptor.py
49
55
  tests/test_errorcodes.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ logger = _errortools.cli:log_main
3
+ python -m errortools = _errortools.cli:main
@@ -0,0 +1 @@
1
+ namebyauthor==1.0.0
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="errortools",
5
- version="1.3.0",
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",
@@ -25,10 +25,11 @@ setup(
25
25
  package_data={"errortools": ["py.typed"]},
26
26
  include_package_data=True,
27
27
  python_requires=">=3.10",
28
- install_requires=[],
28
+ install_requires=["namebyauthor==1.0.0"],
29
29
  entry_points={
30
30
  "console_scripts": [
31
- "errortools = _errortools.cli:main",
31
+ "python -m errortools = _errortools.cli:main",
32
+ "logger = _errortools.cli:log_main",
32
33
  ]
33
34
  },
34
35
  )
@@ -1,12 +1,12 @@
1
- """Tests for `errortools` module. Using pytest."""
2
-
3
- __version__ = "1.3"
4
-
5
- try:
6
- import pytest
7
- except ImportError:
8
- # A constant used to determine whether the pytest module exists.
9
- # If it exists, tests can run normally; otherwise, a warning will be printed.
10
- HAS_PYTEST = False
11
- else:
12
- HAS_PYTEST = True
1
+ """Tests for `errortools` module. Using pytest."""
2
+
3
+ __version__ = "1.5"
4
+
5
+ try:
6
+ import pytest
7
+ except ImportError:
8
+ # A constant used to determine whether the pytest module exists.
9
+ # If it exists, tests can run normally; otherwise, a warning will be printed.
10
+ HAS_PYTEST = False
11
+ else:
12
+ HAS_PYTEST = True
@@ -22,6 +22,7 @@ from . import HAS_PYTEST
22
22
 
23
23
  if not HAS_PYTEST:
24
24
  print("pytest is required to run these tests, skip run test_abc.py")
25
+ exit(0)
25
26
 
26
27
  # =============================================================================
27
28
  # ErrorCodeable ABC Tests
@@ -2,11 +2,12 @@
2
2
 
3
3
  import pytest
4
4
 
5
- from _errortools.cache import error_cache
5
+ from _errortools.decorator.cache import error_cache
6
6
  from . import HAS_PYTEST
7
7
 
8
8
  if not HAS_PYTEST:
9
9
  print("pytest is required to run these tests, skip run test_cache.py")
10
+ exit(0)
10
11
 
11
12
  # =============================================================================
12
13
  # error_cache — basic decoration patterns
@@ -0,0 +1,33 @@
1
+ """Tests for _errortools/const.py — constants."""
2
+
3
+ from _errortools.const import (
4
+ LARGE_ERROR_CACHE_SIZE,
5
+ SMALL_ERROR_CACHE_SIZE,
6
+ DEFAULT_ERROR_CACHE_SIZE,
7
+ UNLIMITED_ERROR_CACHE,
8
+ )
9
+ from . import HAS_PYTEST
10
+
11
+ if not HAS_PYTEST:
12
+ print("pytest is required to run these tests, skip run test_const.py")
13
+ exit(0)
14
+
15
+
16
+ def test_constant_types():
17
+ assert isinstance(LARGE_ERROR_CACHE_SIZE, int)
18
+ assert isinstance(SMALL_ERROR_CACHE_SIZE, int)
19
+ assert isinstance(DEFAULT_ERROR_CACHE_SIZE, int)
20
+ # ``UNLIMITED_ERROR_CACHE`` was NoneType
21
+ assert not isinstance(UNLIMITED_ERROR_CACHE, int)
22
+
23
+
24
+ def test_default_cache_is_valid():
25
+ assert DEFAULT_ERROR_CACHE_SIZE > 0
26
+ assert SMALL_ERROR_CACHE_SIZE <= DEFAULT_ERROR_CACHE_SIZE <= LARGE_ERROR_CACHE_SIZE
27
+
28
+
29
+ def test_cache_constant_value():
30
+ assert DEFAULT_ERROR_CACHE_SIZE == 128
31
+ assert SMALL_ERROR_CACHE_SIZE == 64
32
+ assert LARGE_ERROR_CACHE_SIZE == 1024
33
+ assert UNLIMITED_ERROR_CACHE == None
@@ -9,6 +9,7 @@ from . import HAS_PYTEST
9
9
 
10
10
  if not HAS_PYTEST:
11
11
  print("pytest is required to run these tests, skip run test_decorator.py")
12
+ exit(0)
12
13
 
13
14
  # =============================================================================
14
15
  # deprecated decorator
@@ -8,6 +8,8 @@ from . import HAS_PYTEST
8
8
 
9
9
  if not HAS_PYTEST:
10
10
  print("pytest is required to run these tests, skip run test_descriptor.py")
11
+ exit(0)
12
+
11
13
  # =============================================================================
12
14
  # ErrorMsg Descriptor
13
15
  # =============================================================================
@@ -17,6 +17,8 @@ from . import HAS_PYTEST
17
17
 
18
18
  if not HAS_PYTEST:
19
19
  print("pytest is required to run these tests, skip run test_errorcodes.py")
20
+ exit(0)
21
+
20
22
  # =============================================================================
21
23
  # PureBaseException
22
24
  # =============================================================================
@@ -7,6 +7,8 @@ from . import HAS_PYTEST
7
7
 
8
8
  if not HAS_PYTEST:
9
9
  print("pytest is required to run these tests, skip run test_groups.py")
10
+ exit(0)
11
+
10
12
  # =============================================================================
11
13
  # GroupErrors — collect / raise_group / clear
12
14
  # =============================================================================
@@ -17,6 +17,8 @@ from . import HAS_PYTEST
17
17
 
18
18
  if not HAS_PYTEST:
19
19
  print("pytest is required to run these tests, skip run test_ignore.py")
20
+ exit(0)
21
+
20
22
  # =============================================================================
21
23
  # ignore() (ErrorIgnoreWrapper)
22
24
  # =============================================================================
@@ -23,6 +23,8 @@ from . import HAS_PYTEST
23
23
 
24
24
  if not HAS_PYTEST:
25
25
  print("pytest is required to run these tests, skip run test_logging.py")
26
+ exit(0)
27
+
26
28
  # ---------------------------------------------------------------------------
27
29
  # Helpers
28
30
  # ---------------------------------------------------------------------------
@@ -22,6 +22,8 @@ from . import HAS_PYTEST
22
22
 
23
23
  if not HAS_PYTEST:
24
24
  print("pytest is required to run these tests, skip run test_mixins.py")
25
+ exit(0)
26
+
25
27
  # =============================================================================
26
28
  # ErrorAttrMixin
27
29
  # =============================================================================
@@ -43,6 +43,7 @@ from . import HAS_PYTEST
43
43
 
44
44
  if not HAS_PYTEST:
45
45
  print("pytest is required to run these tests, skip run test_partials.py")
46
+ exit(0)
46
47
 
47
48
 
48
49
  # =============================================================================
@@ -7,6 +7,7 @@ from . import HAS_PYTEST
7
7
 
8
8
  if not HAS_PYTEST:
9
9
  print("pytest is required to run these teststests/test_raises.py")
10
+ exit(0)
10
11
  # =============================================================================
11
12
  # raises()
12
13
  # =============================================================================
@@ -31,6 +31,7 @@ from . import HAS_PYTEST
31
31
 
32
32
  if not HAS_PYTEST:
33
33
  print("pytest is required to run these teststests/test_typing.py")
34
+ exit(0)
34
35
 
35
36
 
36
37
  class TestBaseTypeAliases:
@@ -14,6 +14,8 @@ from . import HAS_PYTEST
14
14
 
15
15
  if not HAS_PYTEST:
16
16
  print("pytest is required to run these teststests/test_warnings.py")
17
+ exit(0)
18
+
17
19
  # =============================================================================
18
20
  # BaseWarning — basic behaviour
19
21
  # =============================================================================
@@ -1,2 +0,0 @@
1
- aiwonderland
2
- qorexdev
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- errortools = _errortools.cli:main
File without changes
File without changes
File without changes