errortools 2.2.0__tar.gz → 2.4.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 (73) hide show
  1. {errortools-2.2.0/errortools.egg-info → errortools-2.4.0}/PKG-INFO +27 -5
  2. {errortools-2.2.0 → errortools-2.4.0}/README.md +25 -4
  3. {errortools-2.2.0 → errortools-2.4.0}/_errortools/_cli.py +33 -32
  4. {errortools-2.2.0 → errortools-2.4.0}/_errortools/classes/abc.py +3 -0
  5. {errortools-2.2.0 → errortools-2.4.0}/_errortools/cli.py +42 -54
  6. {errortools-2.2.0 → errortools-2.4.0}/_errortools/const.py +6 -4
  7. {errortools-2.2.0 → errortools-2.4.0}/_errortools/decorator/deprecated.py +28 -0
  8. errortools-2.4.0/_errortools/errno.py +86 -0
  9. errortools-2.4.0/_errortools/future.py +165 -0
  10. {errortools-2.2.0 → errortools-2.4.0}/_errortools/ignore.py +23 -19
  11. {errortools-2.2.0 → errortools-2.4.0}/_errortools/logging/level.py +4 -4
  12. {errortools-2.2.0 → errortools-2.4.0}/_errortools/typing.py +4 -0
  13. {errortools-2.2.0 → errortools-2.4.0}/_errortools/version.py +2 -2
  14. {errortools-2.2.0 → errortools-2.4.0}/errortools/__init__.py +14 -1
  15. {errortools-2.2.0 → errortools-2.4.0/errortools.egg-info}/PKG-INFO +27 -5
  16. {errortools-2.2.0 → errortools-2.4.0}/errortools.egg-info/SOURCES.txt +3 -1
  17. {errortools-2.2.0 → errortools-2.4.0}/errortools.egg-info/entry_points.txt +1 -1
  18. errortools-2.4.0/errortools.egg-info/requires.txt +2 -0
  19. {errortools-2.2.0 → errortools-2.4.0}/setup.py +3 -3
  20. {errortools-2.2.0 → errortools-2.4.0}/tests/__init__.py +1 -1
  21. errortools-2.2.0/tests/test_cache.py → errortools-2.4.0/tests/test_decorator.py +153 -2
  22. errortools-2.4.0/tests/test_errno.py +115 -0
  23. errortools-2.4.0/tests/test_future.py +302 -0
  24. {errortools-2.2.0 → errortools-2.4.0}/tests/test_typing.py +9 -0
  25. errortools-2.2.0/_errortools/future.py +0 -23
  26. errortools-2.2.0/errortools.egg-info/requires.txt +0 -1
  27. errortools-2.2.0/tests/test_decorator.py +0 -85
  28. {errortools-2.2.0 → errortools-2.4.0}/AUTHORS.txt +0 -0
  29. {errortools-2.2.0 → errortools-2.4.0}/LICENSE.txt +0 -0
  30. {errortools-2.2.0 → errortools-2.4.0}/_errortools/__init__.py +0 -0
  31. {errortools-2.2.0 → errortools-2.4.0}/_errortools/classes/__init__.py +0 -0
  32. {errortools-2.2.0 → errortools-2.4.0}/_errortools/classes/errorcodes.py +0 -0
  33. {errortools-2.2.0 → errortools-2.4.0}/_errortools/classes/group.py +0 -0
  34. {errortools-2.2.0 → errortools-2.4.0}/_errortools/classes/warn.py +0 -0
  35. {errortools-2.2.0 → errortools-2.4.0}/_errortools/decorator/__init__.py +0 -0
  36. {errortools-2.2.0 → errortools-2.4.0}/_errortools/decorator/cache.py +0 -0
  37. {errortools-2.2.0 → errortools-2.4.0}/_errortools/descriptor/__init__.py +0 -0
  38. {errortools-2.2.0 → errortools-2.4.0}/_errortools/descriptor/errormsg.py +0 -0
  39. {errortools-2.2.0 → errortools-2.4.0}/_errortools/descriptor/nonblankmsg.py +0 -0
  40. {errortools-2.2.0 → errortools-2.4.0}/_errortools/logging/__init__.py +0 -0
  41. {errortools-2.2.0 → errortools-2.4.0}/_errortools/logging/base.py +0 -0
  42. {errortools-2.2.0 → errortools-2.4.0}/_errortools/logging/logger.py +0 -0
  43. {errortools-2.2.0 → errortools-2.4.0}/_errortools/logging/record.py +0 -0
  44. {errortools-2.2.0 → errortools-2.4.0}/_errortools/logging/sink.py +0 -0
  45. {errortools-2.2.0 → errortools-2.4.0}/_errortools/metadata.py +0 -0
  46. {errortools-2.2.0 → errortools-2.4.0}/_errortools/methods/__init__.py +0 -0
  47. {errortools-2.2.0 → errortools-2.4.0}/_errortools/methods/errorattr.py +0 -0
  48. {errortools-2.2.0 → errortools-2.4.0}/_errortools/methods/errordelattr.py +0 -0
  49. {errortools-2.2.0 → errortools-2.4.0}/_errortools/methods/errorhasattr.py +0 -0
  50. {errortools-2.2.0 → errortools-2.4.0}/_errortools/methods/errorsetattr.py +0 -0
  51. {errortools-2.2.0 → errortools-2.4.0}/_errortools/partial.py +0 -0
  52. {errortools-2.2.0 → errortools-2.4.0}/_errortools/py.typed +0 -0
  53. {errortools-2.2.0 → errortools-2.4.0}/_errortools/raises.py +0 -0
  54. {errortools-2.2.0 → errortools-2.4.0}/_errortools/wrappers/__init__.py +0 -0
  55. {errortools-2.2.0 → errortools-2.4.0}/_errortools/wrappers/cache.py +0 -0
  56. {errortools-2.2.0 → errortools-2.4.0}/_errortools/wrappers/ignore.py +0 -0
  57. {errortools-2.2.0 → errortools-2.4.0}/errortools/__main__.py +0 -0
  58. {errortools-2.2.0 → errortools-2.4.0}/errortools.egg-info/dependency_links.txt +0 -0
  59. {errortools-2.2.0 → errortools-2.4.0}/errortools.egg-info/top_level.txt +0 -0
  60. {errortools-2.2.0 → errortools-2.4.0}/setup.cfg +0 -0
  61. {errortools-2.2.0 → errortools-2.4.0}/tests/conftest.py +0 -0
  62. {errortools-2.2.0 → errortools-2.4.0}/tests/run_tests.py +0 -0
  63. {errortools-2.2.0 → errortools-2.4.0}/tests/test_abc.py +0 -0
  64. {errortools-2.2.0 → errortools-2.4.0}/tests/test_const.py +0 -0
  65. {errortools-2.2.0 → errortools-2.4.0}/tests/test_descriptor.py +0 -0
  66. {errortools-2.2.0 → errortools-2.4.0}/tests/test_errorcodes.py +0 -0
  67. {errortools-2.2.0 → errortools-2.4.0}/tests/test_groups.py +0 -0
  68. {errortools-2.2.0 → errortools-2.4.0}/tests/test_ignore.py +0 -0
  69. {errortools-2.2.0 → errortools-2.4.0}/tests/test_logging.py +0 -0
  70. {errortools-2.2.0 → errortools-2.4.0}/tests/test_mixins.py +0 -0
  71. {errortools-2.2.0 → errortools-2.4.0}/tests/test_partials.py +0 -0
  72. {errortools-2.2.0 → errortools-2.4.0}/tests/test_raises.py +0 -0
  73. {errortools-2.2.0 → errortools-2.4.0}/tests/test_warnings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: errortools
3
- Version: 2.2.0
3
+ Version: 2.4.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
@@ -20,6 +20,7 @@ Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.txt
21
21
  License-File: AUTHORS.txt
22
22
  Requires-Dist: namebyauthor==1.0.0
23
+ Requires-Dist: typing_extensions>=4.8.0
23
24
  Dynamic: author
24
25
  Dynamic: author-email
25
26
  Dynamic: classifier
@@ -38,6 +39,7 @@ A lightweight Python exception handling utility library.
38
39
  ## Features
39
40
  - **Raise Exceptions**: `raises()`, `raises_all()`, `reraise()` — batch raising and exception conversion
40
41
  - **Catch & Suppress**: `ignore()`, `ignore_subclass()`, `ignore_warns()`, `fast_ignore()`, `super_fast_ignore()`, `timeout()`, `retry()` — graceful suppression of exceptions and warnings, with automatic retry
42
+ - **Future Utilities**: `super_fast_catch()`, `super_fast_reraise()`, `ExceptionCollector` — lightweight exception handling for high-performance scenarios
41
43
  - **Exception Caching**: `error_cache` — cache exceptions raised by functions (similar to `lru_cache`)
42
44
  - **Custom Exceptions**: `PureBaseException`, `ContextException`, `BaseErrorCodes`, `BaseWarning` — structured exception classes with error codes, trace IDs, and context
43
45
  - **Attribute Error Mixin**: Customize error behavior for attribute access, assignment, and deletion
@@ -61,7 +63,7 @@ from errortools import (
61
63
  error_cache,
62
64
  PureBaseException, ContextException, BaseErrorCodes, BaseWarning,
63
65
  )
64
- from errortools.future import super_fast_ignore
66
+ from errortools.future import super_fast_ignore, super_fast_catch, super_fast_reraise, ExceptionCollector
65
67
 
66
68
  # ── 1. ignore ── context manager with full metadata ──────────────────────────
67
69
  with ignore(KeyError) as err:
@@ -153,7 +155,27 @@ raises_all(
153
155
  exc = assert_raises(int, [ValueError], "not-a-number")
154
156
  print(exc) # invalid literal for int() with base 10: 'not-a-number'
155
157
 
156
- # ── 10. error_cache ── cache exceptions by call arguments ─────────────────────
158
+ # ── 10. super_fast_catch ── lightweight exception capture ──────────────────────
159
+ with super_fast_catch(ValueError) as ctx:
160
+ raise ValueError("oops")
161
+
162
+ assert ctx.exception is not None
163
+ print(ctx.exception) # ValueError('oops')
164
+
165
+ # ── 11. super_fast_reraise ── lightweight exception type conversion ────────────
166
+ with super_fast_reraise(KeyError, ValueError):
167
+ raise KeyError("missing") # → ValueError: 'missing'
168
+
169
+ # ── 12. ExceptionCollector ── batch collect exceptions ───────────────────────────
170
+ collector = ExceptionCollector()
171
+ collector.catch(int, "bad1")
172
+ collector.catch(int, "bad2")
173
+
174
+ if collector.has_errors:
175
+ print(f"Collected {collector.count} errors")
176
+ collector.raise_all("batch operation failed") # → ExceptionGroup (2 sub-exceptions)
177
+
178
+ # ── 13. error_cache ── cache exceptions by call arguments ─────────────────────
157
179
  @error_cache(maxsize=64)
158
180
  def load(user_id: int) -> dict:
159
181
  if user_id < 0:
@@ -166,7 +188,7 @@ with ignore(ValueError):
166
188
  print(load.cache_info()) # CacheInfo(hits=0, misses=1, maxsize=64, currsize=1)
167
189
  load.clear_cache()
168
190
 
169
- # ── 11. Custom exceptions — three layers ──────────────────────────────────────
191
+ # ── 14. Custom exceptions — three layers ──────────────────────────────────────
170
192
 
171
193
  # Layer 1: PureBaseException — code + detail only
172
194
  class AppError(PureBaseException):
@@ -202,7 +224,7 @@ raise BaseErrorCodes.runtime_failure("crash") # RuntimeFailure [
202
224
  raise BaseErrorCodes.timeout_failure() # TimeoutFailure [4002]
203
225
  raise BaseErrorCodes.configuration_error("missing key") # ConfigurationError [5001]
204
226
 
205
- # ── 12. BaseWarning ── structured warnings with factory methods ───────────────
227
+ # ── 15. BaseWarning ── structured warnings with factory methods ───────────────
206
228
  class ExperimentalWarning(BaseWarning):
207
229
  default_detail = "This feature is experimental."
208
230
 
@@ -4,6 +4,7 @@ A lightweight Python exception handling utility library.
4
4
  ## Features
5
5
  - **Raise Exceptions**: `raises()`, `raises_all()`, `reraise()` — batch raising and exception conversion
6
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
+ - **Future Utilities**: `super_fast_catch()`, `super_fast_reraise()`, `ExceptionCollector` — lightweight exception handling for high-performance scenarios
7
8
  - **Exception Caching**: `error_cache` — cache exceptions raised by functions (similar to `lru_cache`)
8
9
  - **Custom Exceptions**: `PureBaseException`, `ContextException`, `BaseErrorCodes`, `BaseWarning` — structured exception classes with error codes, trace IDs, and context
9
10
  - **Attribute Error Mixin**: Customize error behavior for attribute access, assignment, and deletion
@@ -27,7 +28,7 @@ from errortools import (
27
28
  error_cache,
28
29
  PureBaseException, ContextException, BaseErrorCodes, BaseWarning,
29
30
  )
30
- from errortools.future import super_fast_ignore
31
+ from errortools.future import super_fast_ignore, super_fast_catch, super_fast_reraise, ExceptionCollector
31
32
 
32
33
  # ── 1. ignore ── context manager with full metadata ──────────────────────────
33
34
  with ignore(KeyError) as err:
@@ -119,7 +120,27 @@ raises_all(
119
120
  exc = assert_raises(int, [ValueError], "not-a-number")
120
121
  print(exc) # invalid literal for int() with base 10: 'not-a-number'
121
122
 
122
- # ── 10. error_cache ── cache exceptions by call arguments ─────────────────────
123
+ # ── 10. super_fast_catch ── lightweight exception capture ──────────────────────
124
+ with super_fast_catch(ValueError) as ctx:
125
+ raise ValueError("oops")
126
+
127
+ assert ctx.exception is not None
128
+ print(ctx.exception) # ValueError('oops')
129
+
130
+ # ── 11. super_fast_reraise ── lightweight exception type conversion ────────────
131
+ with super_fast_reraise(KeyError, ValueError):
132
+ raise KeyError("missing") # → ValueError: 'missing'
133
+
134
+ # ── 12. ExceptionCollector ── batch collect exceptions ───────────────────────────
135
+ collector = ExceptionCollector()
136
+ collector.catch(int, "bad1")
137
+ collector.catch(int, "bad2")
138
+
139
+ if collector.has_errors:
140
+ print(f"Collected {collector.count} errors")
141
+ collector.raise_all("batch operation failed") # → ExceptionGroup (2 sub-exceptions)
142
+
143
+ # ── 13. error_cache ── cache exceptions by call arguments ─────────────────────
123
144
  @error_cache(maxsize=64)
124
145
  def load(user_id: int) -> dict:
125
146
  if user_id < 0:
@@ -132,7 +153,7 @@ with ignore(ValueError):
132
153
  print(load.cache_info()) # CacheInfo(hits=0, misses=1, maxsize=64, currsize=1)
133
154
  load.clear_cache()
134
155
 
135
- # ── 11. Custom exceptions — three layers ──────────────────────────────────────
156
+ # ── 14. Custom exceptions — three layers ──────────────────────────────────────
136
157
 
137
158
  # Layer 1: PureBaseException — code + detail only
138
159
  class AppError(PureBaseException):
@@ -168,7 +189,7 @@ raise BaseErrorCodes.runtime_failure("crash") # RuntimeFailure [
168
189
  raise BaseErrorCodes.timeout_failure() # TimeoutFailure [4002]
169
190
  raise BaseErrorCodes.configuration_error("missing key") # ConfigurationError [5001]
170
191
 
171
- # ── 12. BaseWarning ── structured warnings with factory methods ───────────────
192
+ # ── 15. BaseWarning ── structured warnings with factory methods ───────────────
172
193
  class ExperimentalWarning(BaseWarning):
173
194
  default_detail = "This feature is experimental."
174
195
 
@@ -1,32 +1,33 @@
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__}")
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.set_level("TRACE")
22
+ log.add(stream, level=level, colorize=None)
23
+ log.log(get_level(level), message)
24
+
25
+
26
+ def _print_info() -> None:
27
+ """Print a summary of all package metadata."""
28
+ print(f"errortools v{__version__}")
29
+ print(f" {__description__}")
30
+ print(f" Author: {__author__} <{__author_email__}>")
31
+ print(f" License: {__license__}")
32
+ print(f" URL: {__url__}")
33
+ print(f" Copyright: {__copyright__}")
@@ -5,6 +5,7 @@ import shutil
5
5
  import csv
6
6
  import configparser
7
7
 
8
+ from typing_extensions import disjoint_base # I use 3.14.3
8
9
  from ..methods import (
9
10
  ErrorAttrMixin,
10
11
  ErrorAttrCheckMixin,
@@ -30,6 +31,7 @@ def _check_methods(C: type[Any], *methods: str) -> Union[bool, Literal[NotImplem
30
31
  return True
31
32
 
32
33
 
34
+ @disjoint_base
33
35
  class ErrorAttrable(ABC):
34
36
  """
35
37
  Abstract Base Class (ABC) for classes supporting custom attribute error handling.
@@ -175,6 +177,7 @@ ErrorAttrable.register(ErrorSetAttrMixin)
175
177
  # ----------------------------------------------------------------------
176
178
 
177
179
 
180
+ @disjoint_base
178
181
  class ErrorCodeable(ABC):
179
182
  """Abstract Base Class for exceptions that carry a machine-readable error code.
180
183
 
@@ -14,107 +14,95 @@ from .metadata import (
14
14
  __url__,
15
15
  )
16
16
  from .version import __version__
17
- from tests.run_tests import run_tests
18
17
 
19
18
 
20
19
  def parse_args(args: list[str] | None = None) -> argparse.Namespace:
21
20
  """Parse command line arguments."""
21
+ is_logger = "logger" in sys.argv[0]
22
+
23
+ if is_logger:
24
+ parser = argparse.ArgumentParser(
25
+ prog="logger",
26
+ description="Logger CLI - Emit log messages from command line",
27
+ )
28
+ parser.add_argument("message", help="Log message")
29
+ parser.add_argument(
30
+ "--level",
31
+ "-l",
32
+ default="info",
33
+ choices=[
34
+ "trace",
35
+ "debug",
36
+ "info",
37
+ "success",
38
+ "warning",
39
+ "error",
40
+ "critical",
41
+ ],
42
+ )
43
+ parser.add_argument(
44
+ "--output", "-o", choices=["stderr", "stdout"], default="stderr"
45
+ )
46
+ return parser.parse_args(args)
47
+
48
+ prog = "errortools"
49
+ desc = __description__
50
+
22
51
  if sys.version_info >= (3, 14):
23
- parser = argparse.ArgumentParser(description=__description__, color=True)
52
+ parser = argparse.ArgumentParser(description=desc, prog=prog, color=True)
24
53
  else:
25
- parser = argparse.ArgumentParser(description=__description__)
54
+ parser = argparse.ArgumentParser(description=desc, prog=prog)
26
55
 
27
56
  parser.add_argument(
28
57
  "-v", "--version", action="store_true", help="Show version and exit"
29
58
  )
30
-
31
59
  parser.add_argument(
32
60
  "-c", "--copyrights", action="store_true", help="Show copyright information"
33
61
  )
34
-
35
62
  parser.add_argument("-a", "--author", action="store_true", help="Show author name")
36
-
37
63
  parser.add_argument("-e", "--email", action="store_true", help="Show author email")
38
-
39
64
  parser.add_argument(
40
65
  "-l", "--license", action="store_true", help="Show license type"
41
66
  )
42
-
43
67
  parser.add_argument("-u", "--url", action="store_true", help="Show project URL")
44
-
45
68
  parser.add_argument(
46
69
  "-i", "--info", action="store_true", help="Show all package information"
47
70
  )
48
-
49
71
  parser.add_argument(
50
- "--run-tests",
51
- action="store_true",
52
- help="Run tests for errortools module. (Using pytest)",
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)",
72
+ "--run-tests", action="store_true", help="Run tests using pytest"
78
73
  )
79
74
 
80
75
  return parser.parse_args(args)
81
76
 
82
77
 
83
- def log_main() -> None:
84
- """Logging main CLI entry point."""
78
+ def main() -> None:
85
79
  args = parse_args(sys.argv[1:])
86
80
 
87
- if args.subcommand == "log":
81
+ if "logger" in sys.argv[0]:
88
82
  _cmd_log(args.message, args.level, args.output)
89
-
90
-
91
- def main() -> None:
92
- """Main CLI entry point."""
93
- args = parse_args(sys.argv[1:])
83
+ return
94
84
 
95
85
  if args.version:
96
86
  print(f"v{__version__}")
97
-
98
87
  elif args.copyrights:
99
88
  print(__copyright__)
100
-
101
89
  elif args.author:
102
90
  print(f"Author: {__author__}")
103
-
104
91
  elif args.email:
105
92
  print(f"Email: {__author_email__}")
106
-
107
93
  elif args.license:
108
94
  print(f"License: {__license__}")
109
-
110
95
  elif args.url:
111
96
  print(f"URL: {__url__}")
112
-
113
97
  elif args.run_tests:
114
- run_tests()
98
+ from tests.run_tests import run_tests
115
99
 
100
+ run_tests()
116
101
  elif args.info:
117
102
  _print_info()
118
-
119
103
  else:
120
104
  parse_args(["--help"])
105
+
106
+
107
+ if __name__ == "__main__":
108
+ main()
@@ -1,10 +1,12 @@
1
1
  """Constants for errortools module."""
2
2
 
3
+ from typing import Final
4
+
3
5
  # ------------------------------------------------------------------
4
6
  # error_cache constants
5
7
  # ------------------------------------------------------------------
6
8
 
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
+ DEFAULT_ERROR_CACHE_SIZE: Final[int] = 128
10
+ SMALL_ERROR_CACHE_SIZE: Final[int] = 64
11
+ LARGE_ERROR_CACHE_SIZE: Final[int] = 1024
12
+ UNLIMITED_ERROR_CACHE: Final[None] = None
@@ -31,3 +31,31 @@ def deprecated(
31
31
  return wrapper
32
32
 
33
33
  return decorator
34
+
35
+
36
+ def experimental(
37
+ reason: str = "This function is experimental and may change in future versions.",
38
+ ) -> Callable:
39
+ """Decorator that marks a function as experimental.
40
+
41
+ Emits a UserWarning when the decorated function is called.
42
+
43
+ Args:
44
+ reason: Optional message explaining the experimental status and any caveats.
45
+
46
+ Example:
47
+ >>> @experimental(reason="API may change without notice.")
48
+ ... def new_feature():
49
+ ... pass
50
+ """
51
+
52
+ def decorator(func: Callable) -> Callable:
53
+ @wraps(func)
54
+ def wrapper(*args, **kwargs):
55
+ msg = f"{func.__name__} is experimental. {reason}"
56
+ warnings.warn(msg, UserWarning, stacklevel=2)
57
+ return func(*args, **kwargs)
58
+
59
+ return wrapper
60
+
61
+ return decorator
@@ -0,0 +1,86 @@
1
+ """Lightweight utilities for errno error code inspection and handling."""
2
+
3
+ import errno
4
+
5
+
6
+ def get_errno_name(code: int) -> str | None:
7
+ """Get the symbolic name for an errno code.
8
+
9
+ Args:
10
+ code: The numeric errno code (e.g., 2 for ENOENT)
11
+
12
+ Returns:
13
+ The symbolic errno name (e.g., "ENOENT") or None if not found
14
+ """
15
+ for name in dir(errno):
16
+ if name.isupper() and getattr(errno, name) == code:
17
+ return name
18
+ return None
19
+
20
+
21
+ def get_errno_message(code: int) -> str:
22
+ """Get the corresponding message description for an errno code.
23
+
24
+ Args:
25
+ code: The numeric errno code
26
+
27
+ Returns:
28
+ The message string corresponding to the errno code
29
+
30
+ Raises:
31
+ ValueError: If the given errno code is invalid
32
+ """
33
+ if not is_valid_errno(code):
34
+ raise ValueError(f"Unknown error code: {code}")
35
+
36
+ return errno.errorcode.get(code, f"Unknown error {code}")
37
+
38
+
39
+ def get_all_errno_codes() -> dict[str, int]:
40
+ """Get a dictionary of all errno constant names and their numeric codes.
41
+
42
+ Returns:
43
+ Dictionary mapping uppercase errno names to their integer values
44
+ """
45
+ codes = {}
46
+ for name in dir(errno):
47
+ if name.isupper():
48
+ try:
49
+ value = getattr(errno, name)
50
+ if isinstance(value, int):
51
+ codes[name] = value
52
+ except (AttributeError, TypeError):
53
+ pass
54
+ return codes
55
+
56
+
57
+ def is_valid_errno(code: int) -> bool:
58
+ """Check whether a given integer is a valid system errno code.
59
+
60
+ Args:
61
+ code: The numeric code to validate
62
+
63
+ Returns:
64
+ True if the code corresponds to a known errno constant, False otherwise
65
+ """
66
+ return get_errno_name(code) is not None
67
+
68
+
69
+ def strerror(code: int) -> str:
70
+ """Get the human-readable system error message for an errno code.
71
+
72
+ Args:
73
+ code: The numeric errno code
74
+
75
+ Returns:
76
+ Human-readable error message string
77
+
78
+ Raises:
79
+ ValueError: If the error code is not recognized by the system
80
+ """
81
+ import os
82
+
83
+ try:
84
+ return os.strerror(code)
85
+ except (ValueError, OSError):
86
+ raise ValueError(f"Unknown error code: {code}")
@@ -0,0 +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()