errortools 3.1.0__tar.gz → 3.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. {errortools-3.1.0 → errortools-3.3.0}/AUTHORS.txt +2 -1
  2. errortools-3.3.0/PKG-INFO +152 -0
  3. errortools-3.3.0/README.md +105 -0
  4. errortools-3.3.0/_errortools/__main__.py +109 -0
  5. {errortools-3.1.0 → errortools-3.3.0}/_errortools/_speedup.c +60 -12
  6. errortools-3.3.0/_errortools/classes/__init__.py +1 -0
  7. errortools-3.3.0/_errortools/classes/abc.py +207 -0
  8. errortools-3.3.0/_errortools/classes/errorcodes.py +273 -0
  9. errortools-3.3.0/_errortools/classes/group.py +121 -0
  10. errortools-3.3.0/_errortools/classes/warn.py +124 -0
  11. {errortools-3.1.0 → errortools-3.3.0}/_errortools/cli.py +34 -41
  12. errortools-3.3.0/_errortools/decorator/__init__.py +1 -0
  13. errortools-3.3.0/_errortools/decorator/cache.py +79 -0
  14. errortools-3.3.0/_errortools/decorator/deprecated.py +61 -0
  15. errortools-3.3.0/_errortools/decorator/handlers.py +90 -0
  16. errortools-3.3.0/_errortools/decorator/retry.py +99 -0
  17. errortools-3.3.0/_errortools/decorator/timeout.py +38 -0
  18. errortools-3.3.0/_errortools/descriptor/__init__.py +2 -0
  19. errortools-3.3.0/_errortools/descriptor/base.py +25 -0
  20. errortools-3.3.0/_errortools/descriptor/errormsg.py +33 -0
  21. errortools-3.3.0/_errortools/descriptor/nonblankmsg.py +46 -0
  22. {errortools-3.1.0 → errortools-3.3.0}/_errortools/future.py +14 -8
  23. {errortools-3.1.0 → errortools-3.3.0}/_errortools/ignore.py +2 -135
  24. errortools-3.3.0/_errortools/logging/__init__.py +43 -0
  25. errortools-3.3.0/_errortools/logging/base.py +462 -0
  26. errortools-3.3.0/_errortools/logging/level.py +85 -0
  27. errortools-3.3.0/_errortools/logging/logger.py +13 -0
  28. errortools-3.3.0/_errortools/logging/record.py +116 -0
  29. errortools-3.3.0/_errortools/logging/sink.py +243 -0
  30. {errortools-3.1.0 → errortools-3.3.0}/_errortools/metadata.py +1 -3
  31. {errortools-3.1.0 → errortools-3.3.0}/_errortools/partial.py +5 -13
  32. errortools-3.3.0/_errortools/plugins.py +83 -0
  33. {errortools-3.1.0 → errortools-3.3.0}/_errortools/raises.py +2 -5
  34. errortools-3.3.0/_errortools/version.py +17 -0
  35. errortools-3.3.0/_errortools/wrappers/__init__.py +2 -0
  36. errortools-3.3.0/_errortools/wrappers/cache.py +93 -0
  37. errortools-3.3.0/_errortools/wrappers/ignore.py +120 -0
  38. errortools-3.3.0/docs/conf.py +60 -0
  39. {errortools-3.1.0 → errortools-3.3.0}/errortools/__init__.py +99 -10
  40. errortools-3.3.0/errortools.egg-info/PKG-INFO +152 -0
  41. errortools-3.3.0/errortools.egg-info/SOURCES.txt +79 -0
  42. errortools-3.3.0/errortools.egg-info/requires.txt +2 -0
  43. errortools-3.3.0/pyproject.toml +91 -0
  44. errortools-3.3.0/testing/__init__.py +35 -0
  45. errortools-3.3.0/testing/benchmark/__init__.py +4 -0
  46. errortools-3.3.0/testing/benchmark/test_future_perf.py +239 -0
  47. {errortools-3.1.0 → errortools-3.3.0}/testing/run_tests.py +3 -3
  48. {errortools-3.1.0 → errortools-3.3.0}/testing/test_decorator.py +288 -0
  49. {errortools-3.1.0 → errortools-3.3.0}/testing/test_descriptor.py +54 -10
  50. {errortools-3.1.0 → errortools-3.3.0}/testing/test_groups.py +127 -130
  51. {errortools-3.1.0 → errortools-3.3.0}/testing/test_ignore.py +1 -174
  52. {errortools-3.1.0 → errortools-3.3.0}/testing/test_logging.py +0 -1
  53. errortools-3.3.0/testing/test_plugins.py +105 -0
  54. errortools-3.3.0/testing/test_protocols.py +260 -0
  55. errortools-3.3.0/testing/test_testing/__init__.py +1 -0
  56. errortools-3.3.0/testing/test_testing/test_testing.py +41 -0
  57. errortools-3.3.0/testing/test_version.py +71 -0
  58. errortools-3.1.0/PKG-INFO +0 -367
  59. errortools-3.1.0/README.md +0 -329
  60. errortools-3.1.0/_errortools/const.py +0 -12
  61. errortools-3.1.0/_errortools/version.py +0 -7
  62. errortools-3.1.0/errortools.egg-info/PKG-INFO +0 -367
  63. errortools-3.1.0/errortools.egg-info/SOURCES.txt +0 -47
  64. errortools-3.1.0/errortools.egg-info/requires.txt +0 -2
  65. errortools-3.1.0/setup.py +0 -58
  66. errortools-3.1.0/testing/__init__.py +0 -11
  67. errortools-3.1.0/testing/test_const.py +0 -28
  68. {errortools-3.1.0 → errortools-3.3.0}/LICENSE.txt +0 -0
  69. {errortools-3.1.0 → errortools-3.3.0}/_errortools/__init__.py +0 -0
  70. {errortools-3.1.0 → errortools-3.3.0}/_errortools/_cli.py +0 -0
  71. {errortools-3.1.0 → errortools-3.3.0}/_errortools/errno.py +0 -0
  72. {errortools-3.1.0 → errortools-3.3.0}/_errortools/py.typed +0 -0
  73. {errortools-3.1.0 → errortools-3.3.0}/_errortools/typing.py +0 -0
  74. {errortools-3.1.0 → errortools-3.3.0}/errortools/__main__.py +0 -0
  75. {errortools-3.1.0 → errortools-3.3.0}/errortools/future.py +0 -0
  76. {errortools-3.1.0 → errortools-3.3.0}/errortools/logging.py +0 -0
  77. {errortools-3.1.0 → errortools-3.3.0}/errortools/partial.py +0 -0
  78. {errortools-3.1.0 → errortools-3.3.0}/errortools.egg-info/dependency_links.txt +0 -0
  79. {errortools-3.1.0 → errortools-3.3.0}/errortools.egg-info/entry_points.txt +0 -0
  80. {errortools-3.1.0 → errortools-3.3.0}/errortools.egg-info/top_level.txt +0 -0
  81. {errortools-3.1.0 → errortools-3.3.0}/setup.cfg +0 -0
  82. {errortools-3.1.0 → errortools-3.3.0}/testing/__main__.py +0 -0
  83. {errortools-3.1.0 → errortools-3.3.0}/testing/conftest.py +0 -0
  84. {errortools-3.1.0 → errortools-3.3.0}/testing/test_abc.py +0 -0
  85. {errortools-3.1.0 → errortools-3.3.0}/testing/test_errno.py +0 -0
  86. {errortools-3.1.0 → errortools-3.3.0}/testing/test_errorcodes.py +0 -0
  87. {errortools-3.1.0 → errortools-3.3.0}/testing/test_future.py +0 -0
  88. {errortools-3.1.0 → errortools-3.3.0}/testing/test_partials.py +0 -0
  89. {errortools-3.1.0 → errortools-3.3.0}/testing/test_raises.py +0 -0
  90. {errortools-3.1.0 → errortools-3.3.0}/testing/test_typing.py +0 -0
  91. {errortools-3.1.0 → errortools-3.3.0}/testing/test_warnings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  # (Lines starting with # are comments)
2
2
  # Tips:
3
- # Contributors are not sorted by initials,
3
+ # Contributors are not sorted by initials,
4
4
  # but by contribution time.
5
5
  # Here are the real contributors
6
6
  aiwonderland
@@ -10,3 +10,4 @@ yangphysics
10
10
  # And here are bot contributors
11
11
  dependabot[bot]
12
12
  AbaAbaAba-bot-like[bot]
13
+ pre-commit-ci[bot]
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: errortools
3
+ Version: 3.3.0
4
+ Summary: errortools - a toolset for working with Python exceptions and warnings and logging.
5
+ Author-email: Evan Yang <quantbit@126.com>
6
+ License: Copyright (c) 2026 authors see AUTHORS.txt
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining
9
+ a copy of this software and associated documentation files (the
10
+ "Software"), to deal in the Software without restriction, including
11
+ without limitation the rights to use, copy, modify, merge, publish,
12
+ distribute, sublicense, and/or sell copies of the Software, and to
13
+ permit persons to whom the Software is furnished to do so, subject to
14
+ the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be
17
+ included in all copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+
27
+ Project-URL: Homepage, https://github.com/more-abc/errortools
28
+ Project-URL: Documentation, https://errortools.readthedocs.io/
29
+ Classifier: License :: OSI Approved :: MIT License
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: Programming Language :: Python :: 3.8
32
+ Classifier: Programming Language :: Python :: 3.9
33
+ Classifier: Programming Language :: Python :: 3.10
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Programming Language :: Python :: 3.13
37
+ Classifier: Programming Language :: Python :: 3.14
38
+ Classifier: Operating System :: OS Independent
39
+ Classifier: Typing :: Typed
40
+ Requires-Python: >=3.8
41
+ Description-Content-Type: text/markdown
42
+ License-File: LICENSE.txt
43
+ License-File: AUTHORS.txt
44
+ Requires-Dist: namebyauthor==1.0.0
45
+ Requires-Dist: typing_extensions>=4.15.0
46
+ Dynamic: license-file
47
+
48
+ # errortools
49
+
50
+ A lightweight Python exception handling utility library.
51
+
52
+ [![Code Style: Google](https://img.shields.io/badge/style-google-3666d6.svg)](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
53
+ [![PyPI Version](https://img.shields.io/pypi/v/errortools)](https://pypi.org/project/errortools/)
54
+ [![Python Versions](https://img.shields.io/pypi/pyversions/errortools)](https://pypi.org/project/errortools/)
55
+ ![This week commits](https://img.shields.io/github/commit-activity/w/more-abc/errortools)
56
+ ![This month commits](https://img.shields.io/github/commit-activity/m/more-abc/errortools)
57
+ ![Past year commits](https://img.shields.io/github/commit-activity/y/more-abc/errortools)
58
+ ![Total commits badge](https://img.shields.io/github/commit-activity/t/more-abc/errortools)
59
+ ![OS support](https://img.shields.io/badge/OS-macOS%20Linux%20Windows-red)
60
+
61
+ ## Installation
62
+ ### Use pip...
63
+ ```bash
64
+ pip install errortools
65
+ ```
66
+ ### ...or uv
67
+ ```bash
68
+ uv add errortools
69
+ ```
70
+
71
+ ## Features
72
+
73
+ - **Suppress**: `ignore()`, `super_fast_ignore()`, `@suppress()` — silence exceptions gracefully
74
+ - **Retry & Timeout**: `@retry()`, `@timeout()` — auto retry with delay, async timeout
75
+ - **Raise & Convert**: `raises()`, `reraise()`, `@convert()` — batch raise, type conversion
76
+ - **Catch & Collect**: `super_fast_catch()`, `ExceptionCollector` — inspect or batch exceptions
77
+ - **Caching**: `@error_cache` — LRU exception cache, like `functools.lru_cache`
78
+ - **Custom Exceptions**: `PureBaseException`, `ContextException`, `BaseErrorCodes`
79
+ - **Logging**: structured logger with sinks, context binding, and exception capture
80
+
81
+ ## Quick Start
82
+
83
+ ```python
84
+ from errortools import ignore, retry, reraise, error_cache, suppress, convert
85
+ from errortools.future import super_fast_ignore, super_fast_catch, ExceptionCollector
86
+
87
+ # ── Suppress ─────────────────────────────────────────────────
88
+ with ignore(KeyError) as err: # full metadata
89
+ _ = {}["missing"]
90
+ # err.be_ignore=True, err.name='KeyError', err.traceback=...
91
+
92
+ with super_fast_ignore(ValueError): # zero-overhead
93
+ int("bad")
94
+
95
+ @suppress(ZeroDivisionError, default=0) # decorator form
96
+ def divide(a, b): return a / b
97
+
98
+ # ── Retry ────────────────────────────────────────────────────
99
+ @retry(times=3, on=ConnectionError, delay=1.0)
100
+ def connect(host: str): ...
101
+
102
+ # ── Convert ──────────────────────────────────────────────────
103
+ @convert(KeyError, ValueError) # decorator
104
+ def lookup(d, key): return d[key]
105
+
106
+ with reraise(KeyError, ValueError): # context manager
107
+ raise KeyError("x") # → ValueError
108
+
109
+ # ── Catch & Collect ──────────────────────────────────────────
110
+ with super_fast_catch(ValueError) as ctx:
111
+ raise ValueError("oops")
112
+ # ctx.exception → ValueError('oops')
113
+
114
+ collector = ExceptionCollector()
115
+ collector.catch(int, "bad1")
116
+ collector.catch(int, "bad2")
117
+ collector.raise_all() # → ExceptionGroup
118
+
119
+ # ── Cache ────────────────────────────────────────────────────
120
+ @error_cache(maxsize=64)
121
+ def load(uid: int):
122
+ if uid < 0: raise ValueError(f"invalid: {uid}")
123
+ return {"id": uid}
124
+ ```
125
+
126
+ ## Custom Exceptions
127
+
128
+ ```python
129
+ from errortools import PureBaseException, ContextException, BaseErrorCodes
130
+
131
+ class AppError(PureBaseException):
132
+ code = 9000
133
+ default_detail = "Application error."
134
+
135
+ err = ContextException("failed").with_context(service="db")
136
+ raise BaseErrorCodes.not_found("user #42") # NotFoundError [3001]
137
+ ```
138
+
139
+ ## Logging
140
+
141
+ ```python
142
+ from errortools.logging import logger
143
+
144
+ logger.info("Server started on port {}", 8080)
145
+ logger.add("app.log", rotation=10_485_760, retention=5)
146
+ with logger.catch():
147
+ int("not a number") # logged + suppressed
148
+ ```
149
+
150
+ ## Documentation
151
+
152
+ Full docs: [docs](https://errortools.readthedocs.io) | License: [LICENSE](LICENSE.txt)
@@ -0,0 +1,105 @@
1
+ # errortools
2
+
3
+ A lightweight Python exception handling utility library.
4
+
5
+ [![Code Style: Google](https://img.shields.io/badge/style-google-3666d6.svg)](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
6
+ [![PyPI Version](https://img.shields.io/pypi/v/errortools)](https://pypi.org/project/errortools/)
7
+ [![Python Versions](https://img.shields.io/pypi/pyversions/errortools)](https://pypi.org/project/errortools/)
8
+ ![This week commits](https://img.shields.io/github/commit-activity/w/more-abc/errortools)
9
+ ![This month commits](https://img.shields.io/github/commit-activity/m/more-abc/errortools)
10
+ ![Past year commits](https://img.shields.io/github/commit-activity/y/more-abc/errortools)
11
+ ![Total commits badge](https://img.shields.io/github/commit-activity/t/more-abc/errortools)
12
+ ![OS support](https://img.shields.io/badge/OS-macOS%20Linux%20Windows-red)
13
+
14
+ ## Installation
15
+ ### Use pip...
16
+ ```bash
17
+ pip install errortools
18
+ ```
19
+ ### ...or uv
20
+ ```bash
21
+ uv add errortools
22
+ ```
23
+
24
+ ## Features
25
+
26
+ - **Suppress**: `ignore()`, `super_fast_ignore()`, `@suppress()` — silence exceptions gracefully
27
+ - **Retry & Timeout**: `@retry()`, `@timeout()` — auto retry with delay, async timeout
28
+ - **Raise & Convert**: `raises()`, `reraise()`, `@convert()` — batch raise, type conversion
29
+ - **Catch & Collect**: `super_fast_catch()`, `ExceptionCollector` — inspect or batch exceptions
30
+ - **Caching**: `@error_cache` — LRU exception cache, like `functools.lru_cache`
31
+ - **Custom Exceptions**: `PureBaseException`, `ContextException`, `BaseErrorCodes`
32
+ - **Logging**: structured logger with sinks, context binding, and exception capture
33
+
34
+ ## Quick Start
35
+
36
+ ```python
37
+ from errortools import ignore, retry, reraise, error_cache, suppress, convert
38
+ from errortools.future import super_fast_ignore, super_fast_catch, ExceptionCollector
39
+
40
+ # ── Suppress ─────────────────────────────────────────────────
41
+ with ignore(KeyError) as err: # full metadata
42
+ _ = {}["missing"]
43
+ # err.be_ignore=True, err.name='KeyError', err.traceback=...
44
+
45
+ with super_fast_ignore(ValueError): # zero-overhead
46
+ int("bad")
47
+
48
+ @suppress(ZeroDivisionError, default=0) # decorator form
49
+ def divide(a, b): return a / b
50
+
51
+ # ── Retry ────────────────────────────────────────────────────
52
+ @retry(times=3, on=ConnectionError, delay=1.0)
53
+ def connect(host: str): ...
54
+
55
+ # ── Convert ──────────────────────────────────────────────────
56
+ @convert(KeyError, ValueError) # decorator
57
+ def lookup(d, key): return d[key]
58
+
59
+ with reraise(KeyError, ValueError): # context manager
60
+ raise KeyError("x") # → ValueError
61
+
62
+ # ── Catch & Collect ──────────────────────────────────────────
63
+ with super_fast_catch(ValueError) as ctx:
64
+ raise ValueError("oops")
65
+ # ctx.exception → ValueError('oops')
66
+
67
+ collector = ExceptionCollector()
68
+ collector.catch(int, "bad1")
69
+ collector.catch(int, "bad2")
70
+ collector.raise_all() # → ExceptionGroup
71
+
72
+ # ── Cache ────────────────────────────────────────────────────
73
+ @error_cache(maxsize=64)
74
+ def load(uid: int):
75
+ if uid < 0: raise ValueError(f"invalid: {uid}")
76
+ return {"id": uid}
77
+ ```
78
+
79
+ ## Custom Exceptions
80
+
81
+ ```python
82
+ from errortools import PureBaseException, ContextException, BaseErrorCodes
83
+
84
+ class AppError(PureBaseException):
85
+ code = 9000
86
+ default_detail = "Application error."
87
+
88
+ err = ContextException("failed").with_context(service="db")
89
+ raise BaseErrorCodes.not_found("user #42") # NotFoundError [3001]
90
+ ```
91
+
92
+ ## Logging
93
+
94
+ ```python
95
+ from errortools.logging import logger
96
+
97
+ logger.info("Server started on port {}", 8080)
98
+ logger.add("app.log", rotation=10_485_760, retention=5)
99
+ with logger.catch():
100
+ int("not a number") # logged + suppressed
101
+ ```
102
+
103
+ ## Documentation
104
+
105
+ Full docs: [docs](https://errortools.readthedocs.io) | License: [LICENSE](LICENSE.txt)
@@ -0,0 +1,109 @@
1
+ """Private debug CLI — run via `python -m _errortools`."""
2
+
3
+ import argparse
4
+ import shutil
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from _errortools.version import __version__
9
+
10
+
11
+ def _parse_args(args: list[str] | None = None) -> argparse.Namespace:
12
+ desc = "errortools internal debug tools"
13
+
14
+ if sys.version_info >= (3, 14):
15
+ parser = argparse.ArgumentParser(prog="_errortools", description=desc, color=True)
16
+ else:
17
+ parser = argparse.ArgumentParser(prog="_errortools", description=desc)
18
+
19
+ parser.add_argument("--debug", action="store_true", help="Show debug/environment information")
20
+ parser.add_argument(
21
+ "--reset", action="store_true", help="Clear all cached data (__pycache__, htmlcov, .pytest_cache, .mypy_cache)"
22
+ )
23
+ parser.add_argument("--check", action="store_true", help="Verify installation and dependencies")
24
+
25
+ return parser.parse_args(args)
26
+
27
+
28
+ def _debug_info() -> None:
29
+ import platform
30
+
31
+ print(f"errortools v{__version__}")
32
+ print(f" Python: {sys.version}")
33
+ print(f" Platform: {platform.platform()}")
34
+ print(f" Arch: {platform.machine()}")
35
+ print(f" Prefix: {sys.prefix}")
36
+ print(f" Exec: {sys.executable}")
37
+
38
+ try:
39
+ __import__("_errortools._speedup")
40
+ print(" C speedup: available")
41
+ except ImportError:
42
+ print(" C speedup: not available (pure Python fallback)")
43
+
44
+
45
+ def _reset_cache() -> None:
46
+ root = Path(__file__).resolve().parent.parent
47
+ count = 0
48
+
49
+ for d in root.rglob("__pycache__"):
50
+ if d.is_dir():
51
+ shutil.rmtree(d)
52
+ count += 1
53
+
54
+ for name in ["htmlcov", ".pytest_cache", ".mypy_cache"]:
55
+ d = root / name
56
+ if d.is_dir():
57
+ shutil.rmtree(d)
58
+ count += 1
59
+
60
+ print(f"Reset complete. Cleared {count} cached directories.")
61
+
62
+
63
+ def _check_install() -> None:
64
+ checks: list[tuple[str, bool]] = [("errortools importable", True)]
65
+
66
+ for label, import_path in [
67
+ ("C extension (_speedup)", "_errortools._speedup"),
68
+ ("pytest", "pytest"),
69
+ ("logging module", "_errortools.logging"),
70
+ ("future module", "_errortools.future"),
71
+ ("typing_extensions", "typing_extensions"),
72
+ ]:
73
+ try:
74
+ __import__(import_path)
75
+ checks.append((label, True))
76
+ except ImportError:
77
+ checks.append((label, False))
78
+
79
+ for name, ok in checks:
80
+ status = "OK" if ok else "MISSING"
81
+ print(f" [{status:>7s}] {name}")
82
+
83
+ print()
84
+ if all(ok for _, ok in checks):
85
+ print("All checks passed.")
86
+ else:
87
+ print("Some checks failed. Install missing dependencies.")
88
+
89
+
90
+ _FLAG_ACTIONS = {
91
+ "debug": _debug_info,
92
+ "reset": _reset_cache,
93
+ "check": _check_install,
94
+ }
95
+
96
+
97
+ def main() -> None:
98
+ args = _parse_args(sys.argv[1:])
99
+
100
+ for flag, action in _FLAG_ACTIONS.items():
101
+ if getattr(args, flag, False):
102
+ action()
103
+ return
104
+
105
+ _parse_args(["--help"])
106
+
107
+
108
+ if __name__ == "__main__":
109
+ main()
@@ -2,23 +2,23 @@
2
2
  #include <Python.h>
3
3
 
4
4
  /* Fast exception type checking
5
- *
5
+ *
6
6
  * Optimized check for exception type hierarchy using PyObject_IsSubclass.
7
7
  * Returns False immediately if typ is None, otherwise checks if typ is a
8
8
  * subclass of excs.
9
- *
9
+ *
10
10
  * Args:
11
11
  * typ: The type object to check (or None)
12
12
  * excs: The exception class(es) to check against
13
- *
13
+ *
14
14
  * Returns:
15
15
  * True if typ is a subclass of excs, False otherwise
16
16
  * NULL on error with exception set
17
17
  */
18
18
  static PyObject* fast_issubclass_check(PyObject* self, PyObject* const* args, Py_ssize_t nargs) {
19
19
  if (nargs != 2) {
20
- PyErr_Format(PyExc_TypeError,
21
- "fast_issubclass_check() takes exactly 2 arguments (%zd given)",
20
+ PyErr_Format(PyExc_TypeError,
21
+ "fast_issubclass_check() takes exactly 2 arguments (%zd given)",
22
22
  nargs);
23
23
  return NULL;
24
24
  }
@@ -33,7 +33,7 @@ static PyObject* fast_issubclass_check(PyObject* self, PyObject* const* args, Py
33
33
 
34
34
  /* Validate that excs is a class or tuple of classes */
35
35
  if (!PyType_Check(excs) && !PyTuple_Check(excs)) {
36
- PyErr_SetString(PyExc_TypeError,
36
+ PyErr_SetString(PyExc_TypeError,
37
37
  "second argument must be a class or tuple of classes");
38
38
  return NULL;
39
39
  }
@@ -52,21 +52,21 @@ static PyObject* fast_issubclass_check(PyObject* self, PyObject* const* args, Py
52
52
  }
53
53
 
54
54
  /* Fast exception collector append
55
- *
55
+ *
56
56
  * Optimized append operation for adding exceptions to a list.
57
- *
57
+ *
58
58
  * Args:
59
59
  * list: The list object to append to
60
60
  * exc: The exception object to append
61
- *
61
+ *
62
62
  * Returns:
63
63
  * None on success
64
64
  * NULL on error with exception set
65
65
  */
66
66
  static PyObject* fast_append_exception(PyObject* self, PyObject* const* args, Py_ssize_t nargs) {
67
67
  if (nargs != 2) {
68
- PyErr_Format(PyExc_TypeError,
69
- "fast_append_exception() takes exactly 2 arguments (%zd given)",
68
+ PyErr_Format(PyExc_TypeError,
69
+ "fast_append_exception() takes exactly 2 arguments (%zd given)",
70
70
  nargs);
71
71
  return NULL;
72
72
  }
@@ -87,6 +87,45 @@ static PyObject* fast_append_exception(PyObject* self, PyObject* const* args, Py
87
87
  Py_RETURN_NONE;
88
88
  }
89
89
 
90
+ /* Fast suppress exit for context managers
91
+ *
92
+ * Combined None-check + issubclass optimized for __exit__ methods.
93
+ * Returns True (suppress) if typ is not None and is a subclass of excs,
94
+ * False otherwise. Never raises on None — just returns False.
95
+ *
96
+ * Args:
97
+ * typ: The exception type (or None if no exception)
98
+ * excs: The exception class(es) to match against (tuple)
99
+ *
100
+ * Returns:
101
+ * True if exception should be suppressed, False otherwise
102
+ * NULL on error with exception set
103
+ */
104
+ static PyObject* fast_suppress_exit(PyObject* self, PyObject* const* args, Py_ssize_t nargs) {
105
+ if (nargs != 2) {
106
+ PyErr_Format(PyExc_TypeError,
107
+ "fast_suppress_exit() takes exactly 2 arguments (%zd given)",
108
+ nargs);
109
+ return NULL;
110
+ }
111
+
112
+ PyObject *typ = args[0];
113
+
114
+ if (typ == Py_None) {
115
+ Py_RETURN_FALSE;
116
+ }
117
+
118
+ int result = PyObject_IsSubclass(typ, args[1]);
119
+ if (result == -1) {
120
+ return NULL;
121
+ }
122
+
123
+ if (result) {
124
+ Py_RETURN_TRUE;
125
+ }
126
+ Py_RETURN_FALSE;
127
+ }
128
+
90
129
  /* Method definitions */
91
130
  static PyMethodDef SpeedupMethods[] = {
92
131
  {
@@ -106,6 +145,14 @@ static PyMethodDef SpeedupMethods[] = {
106
145
  "Append an exception to a list with minimal overhead.\n"
107
146
  "Validates that the first argument is a list."
108
147
  },
148
+ {
149
+ "fast_suppress_exit",
150
+ (PyCFunction)fast_suppress_exit,
151
+ METH_FASTCALL,
152
+ "fast_suppress_exit(typ, excs) -> bool\n\n"
153
+ "Return True if typ is not None and is a subclass of excs.\n"
154
+ "Optimized for context manager __exit__ methods."
155
+ },
109
156
  {NULL, NULL, 0, NULL} /* Sentinel */
110
157
  };
111
158
 
@@ -116,7 +163,8 @@ static struct PyModuleDef speedupmodule = {
116
163
  "C speedup module for errortools\n\n"
117
164
  "Provides optimized implementations of exception handling operations:\n"
118
165
  " - fast_issubclass_check: Quick exception type hierarchy checking\n"
119
- " - fast_append_exception: Efficient exception list append operations",
166
+ " - fast_append_exception: Efficient exception list append operations\n"
167
+ " - fast_suppress_exit: Combined None-check + issubclass for __exit__",
120
168
  -1, /* size */
121
169
  SpeedupMethods /* methods */
122
170
  };
@@ -0,0 +1 @@
1
+ """Base classes."""