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.
- {errortools-3.1.0 → errortools-3.3.0}/AUTHORS.txt +2 -1
- errortools-3.3.0/PKG-INFO +152 -0
- errortools-3.3.0/README.md +105 -0
- errortools-3.3.0/_errortools/__main__.py +109 -0
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/_speedup.c +60 -12
- errortools-3.3.0/_errortools/classes/__init__.py +1 -0
- errortools-3.3.0/_errortools/classes/abc.py +207 -0
- errortools-3.3.0/_errortools/classes/errorcodes.py +273 -0
- errortools-3.3.0/_errortools/classes/group.py +121 -0
- errortools-3.3.0/_errortools/classes/warn.py +124 -0
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/cli.py +34 -41
- errortools-3.3.0/_errortools/decorator/__init__.py +1 -0
- errortools-3.3.0/_errortools/decorator/cache.py +79 -0
- errortools-3.3.0/_errortools/decorator/deprecated.py +61 -0
- errortools-3.3.0/_errortools/decorator/handlers.py +90 -0
- errortools-3.3.0/_errortools/decorator/retry.py +99 -0
- errortools-3.3.0/_errortools/decorator/timeout.py +38 -0
- errortools-3.3.0/_errortools/descriptor/__init__.py +2 -0
- errortools-3.3.0/_errortools/descriptor/base.py +25 -0
- errortools-3.3.0/_errortools/descriptor/errormsg.py +33 -0
- errortools-3.3.0/_errortools/descriptor/nonblankmsg.py +46 -0
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/future.py +14 -8
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/ignore.py +2 -135
- errortools-3.3.0/_errortools/logging/__init__.py +43 -0
- errortools-3.3.0/_errortools/logging/base.py +462 -0
- errortools-3.3.0/_errortools/logging/level.py +85 -0
- errortools-3.3.0/_errortools/logging/logger.py +13 -0
- errortools-3.3.0/_errortools/logging/record.py +116 -0
- errortools-3.3.0/_errortools/logging/sink.py +243 -0
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/metadata.py +1 -3
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/partial.py +5 -13
- errortools-3.3.0/_errortools/plugins.py +83 -0
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/raises.py +2 -5
- errortools-3.3.0/_errortools/version.py +17 -0
- errortools-3.3.0/_errortools/wrappers/__init__.py +2 -0
- errortools-3.3.0/_errortools/wrappers/cache.py +93 -0
- errortools-3.3.0/_errortools/wrappers/ignore.py +120 -0
- errortools-3.3.0/docs/conf.py +60 -0
- {errortools-3.1.0 → errortools-3.3.0}/errortools/__init__.py +99 -10
- errortools-3.3.0/errortools.egg-info/PKG-INFO +152 -0
- errortools-3.3.0/errortools.egg-info/SOURCES.txt +79 -0
- errortools-3.3.0/errortools.egg-info/requires.txt +2 -0
- errortools-3.3.0/pyproject.toml +91 -0
- errortools-3.3.0/testing/__init__.py +35 -0
- errortools-3.3.0/testing/benchmark/__init__.py +4 -0
- errortools-3.3.0/testing/benchmark/test_future_perf.py +239 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/run_tests.py +3 -3
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_decorator.py +288 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_descriptor.py +54 -10
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_groups.py +127 -130
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_ignore.py +1 -174
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_logging.py +0 -1
- errortools-3.3.0/testing/test_plugins.py +105 -0
- errortools-3.3.0/testing/test_protocols.py +260 -0
- errortools-3.3.0/testing/test_testing/__init__.py +1 -0
- errortools-3.3.0/testing/test_testing/test_testing.py +41 -0
- errortools-3.3.0/testing/test_version.py +71 -0
- errortools-3.1.0/PKG-INFO +0 -367
- errortools-3.1.0/README.md +0 -329
- errortools-3.1.0/_errortools/const.py +0 -12
- errortools-3.1.0/_errortools/version.py +0 -7
- errortools-3.1.0/errortools.egg-info/PKG-INFO +0 -367
- errortools-3.1.0/errortools.egg-info/SOURCES.txt +0 -47
- errortools-3.1.0/errortools.egg-info/requires.txt +0 -2
- errortools-3.1.0/setup.py +0 -58
- errortools-3.1.0/testing/__init__.py +0 -11
- errortools-3.1.0/testing/test_const.py +0 -28
- {errortools-3.1.0 → errortools-3.3.0}/LICENSE.txt +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/__init__.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/_cli.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/errno.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/py.typed +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/_errortools/typing.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/errortools/__main__.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/errortools/future.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/errortools/logging.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/errortools/partial.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/errortools.egg-info/dependency_links.txt +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/errortools.egg-info/entry_points.txt +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/errortools.egg-info/top_level.txt +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/setup.cfg +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/__main__.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/conftest.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_abc.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_errno.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_errorcodes.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_future.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_partials.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_raises.py +0 -0
- {errortools-3.1.0 → errortools-3.3.0}/testing/test_typing.py +0 -0
- {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
|
+
[](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
|
|
53
|
+
[](https://pypi.org/project/errortools/)
|
|
54
|
+
[](https://pypi.org/project/errortools/)
|
|
55
|
+

|
|
56
|
+

|
|
57
|
+

|
|
58
|
+

|
|
59
|
+

|
|
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
|
+
[](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings)
|
|
6
|
+
[](https://pypi.org/project/errortools/)
|
|
7
|
+
[](https://pypi.org/project/errortools/)
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
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."""
|