errortools 3.3.0__tar.gz → 3.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.
- {errortools-3.3.0/errortools.egg-info → errortools-3.4.0}/PKG-INFO +1 -1
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/classes/abc.py +1 -17
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/logging/logger.py +1 -1
- errortools-3.4.0/_errortools/plugins.py +134 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/version.py +1 -1
- {errortools-3.3.0 → errortools-3.4.0}/errortools/__init__.py +16 -8
- {errortools-3.3.0 → errortools-3.4.0/errortools.egg-info}/PKG-INFO +1 -1
- {errortools-3.3.0 → errortools-3.4.0}/errortools.egg-info/SOURCES.txt +0 -8
- {errortools-3.3.0 → errortools-3.4.0}/pyproject.toml +1 -1
- {errortools-3.3.0 → errortools-3.4.0}/testing/__init__.py +1 -1
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_plugins.py +200 -105
- errortools-3.3.0/_errortools/plugins.py +0 -83
- errortools-3.3.0/docs/conf.py +0 -60
- errortools-3.3.0/testing/test_abc.py +0 -297
- errortools-3.3.0/testing/test_errorcodes.py +0 -395
- errortools-3.3.0/testing/test_future.py +0 -296
- errortools-3.3.0/testing/test_logging.py +0 -673
- errortools-3.3.0/testing/test_partials.py +0 -228
- errortools-3.3.0/testing/test_protocols.py +0 -260
- errortools-3.3.0/testing/test_warnings.py +0 -151
- {errortools-3.3.0 → errortools-3.4.0}/AUTHORS.txt +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/LICENSE.txt +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/README.md +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/__init__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/__main__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/_cli.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/_speedup.c +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/classes/__init__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/classes/errorcodes.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/classes/group.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/classes/warn.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/cli.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/decorator/__init__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/decorator/cache.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/decorator/deprecated.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/decorator/handlers.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/decorator/retry.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/decorator/timeout.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/descriptor/__init__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/descriptor/base.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/descriptor/errormsg.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/descriptor/nonblankmsg.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/errno.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/future.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/ignore.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/logging/__init__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/logging/base.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/logging/level.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/logging/record.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/logging/sink.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/metadata.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/partial.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/py.typed +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/raises.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/typing.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/wrappers/__init__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/wrappers/cache.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/_errortools/wrappers/ignore.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/errortools/__main__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/errortools/future.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/errortools/logging.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/errortools/partial.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/errortools.egg-info/dependency_links.txt +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/errortools.egg-info/entry_points.txt +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/errortools.egg-info/requires.txt +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/errortools.egg-info/top_level.txt +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/setup.cfg +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/__main__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/benchmark/__init__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/benchmark/test_future_perf.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/conftest.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/run_tests.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_decorator.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_descriptor.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_errno.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_groups.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_ignore.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_raises.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_testing/__init__.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_testing/test_testing.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_typing.py +0 -0
- {errortools-3.3.0 → errortools-3.4.0}/testing/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: errortools
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: errortools - a toolset for working with Python exceptions and warnings and logging.
|
|
5
5
|
Author-email: Evan Yang <quantbit@126.com>
|
|
6
6
|
License: Copyright (c) 2026 authors see AUTHORS.txt
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Any, Literal, Union
|
|
2
2
|
from abc import ABC, abstractmethod
|
|
3
|
+
from _collections_abc import _check_methods # type: ignore[attr-defined]
|
|
3
4
|
import copy
|
|
4
5
|
import shutil
|
|
5
6
|
import csv
|
|
@@ -12,23 +13,6 @@ else:
|
|
|
12
13
|
from typing_extensions import disjoint_base
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
def _check_methods(C: type[Any], *methods: str) -> Union[bool, Literal[NotImplemented]]: # type: ignore
|
|
16
|
-
"""Check methods in `C`. If has, return `True`, else `NotImplemented`."""
|
|
17
|
-
# from `_collections_abc.py`.
|
|
18
|
-
# Copyright 2007 Google, Inc. All Rights Reserved.
|
|
19
|
-
# Licensed to PSF under a Contributor Agreement.
|
|
20
|
-
mro: tuple[type[Any], ...] = C.__mro__ # Added type hints for mro var
|
|
21
|
-
for method in methods:
|
|
22
|
-
for B in mro:
|
|
23
|
-
if method in B.__dict__:
|
|
24
|
-
if B.__dict__[method] is None:
|
|
25
|
-
return NotImplemented
|
|
26
|
-
break
|
|
27
|
-
else:
|
|
28
|
-
return NotImplemented
|
|
29
|
-
return True
|
|
30
|
-
|
|
31
|
-
|
|
32
16
|
# ----------------------------------------------------------------------
|
|
33
17
|
# ErrorCodeable
|
|
34
18
|
# ----------------------------------------------------------------------
|
|
@@ -10,4 +10,4 @@ from .level import Level
|
|
|
10
10
|
# Create the default global logger.
|
|
11
11
|
# It ships with a single stderr sink at DEBUG level (mirrors loguru's default).
|
|
12
12
|
logger: BaseLogger = BaseLogger(name="errortools")
|
|
13
|
-
logger.add(sys.stderr, level=Level.
|
|
13
|
+
logger.add(sys.stderr, level=Level.TRACE, colorize=None)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Ultra-lightweight plugin system for errortools."""
|
|
2
|
+
|
|
3
|
+
from typing import Callable, Any
|
|
4
|
+
|
|
5
|
+
_REGISTRY: dict[str, Callable[..., Any]] = {}
|
|
6
|
+
_UNSET = object()
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"register",
|
|
10
|
+
"get",
|
|
11
|
+
"has",
|
|
12
|
+
"list_all",
|
|
13
|
+
"run",
|
|
14
|
+
"remove",
|
|
15
|
+
"clear",
|
|
16
|
+
"Registry",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register(name: str) -> Callable:
|
|
21
|
+
"""Register plugin (decorator).
|
|
22
|
+
|
|
23
|
+
.. versionadded:: 3.2
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def decorator(func: Callable) -> Callable:
|
|
27
|
+
_REGISTRY[name] = func
|
|
28
|
+
return func
|
|
29
|
+
|
|
30
|
+
return decorator
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get(name: str, default: Any = _UNSET) -> Any:
|
|
34
|
+
"""Get registered plugin.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
name: Plugin identifier.
|
|
38
|
+
default: Value returned when the plugin is missing.
|
|
39
|
+
If not provided, a `ValueError` is raised instead.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ValueError: If the plugin does not exist and no *default* was supplied.
|
|
43
|
+
|
|
44
|
+
.. versionadded:: 3.2
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
return _REGISTRY[name]
|
|
48
|
+
except KeyError:
|
|
49
|
+
if default is not _UNSET:
|
|
50
|
+
return default
|
|
51
|
+
raise ValueError(f"Plugin {name!r} is not registered")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def has(name: str) -> bool:
|
|
55
|
+
"""Check whether a plugin is registered.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name: Plugin identifier.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
``True`` if a plugin with the given *name* is registered,
|
|
62
|
+
otherwise ``False``.
|
|
63
|
+
|
|
64
|
+
.. versionadded:: 3.3
|
|
65
|
+
"""
|
|
66
|
+
return name in _REGISTRY
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def remove(name: str) -> None:
|
|
70
|
+
"""Remove a plugin.
|
|
71
|
+
|
|
72
|
+
This is a no-op if the plugin does not exist.
|
|
73
|
+
|
|
74
|
+
.. versionadded:: 3.2
|
|
75
|
+
"""
|
|
76
|
+
_REGISTRY.pop(name, None)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def clear() -> None:
|
|
80
|
+
"""Remove all plugins from the registry.
|
|
81
|
+
|
|
82
|
+
.. versionadded:: 3.3
|
|
83
|
+
"""
|
|
84
|
+
_REGISTRY.clear()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def list_all() -> list[str]:
|
|
88
|
+
"""List all plugin names.
|
|
89
|
+
|
|
90
|
+
.. versionadded:: 3.2
|
|
91
|
+
"""
|
|
92
|
+
return list(_REGISTRY.keys())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def run(name: str, *args, **kwargs) -> Any:
|
|
96
|
+
"""Run plugin.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
ValueError: If the plugin does not exist.
|
|
100
|
+
|
|
101
|
+
.. versionadded:: 3.2
|
|
102
|
+
"""
|
|
103
|
+
return get(name)(*args, **kwargs)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Registry:
|
|
107
|
+
"""Static class providing an alternative API for the plugin registry.
|
|
108
|
+
|
|
109
|
+
.. versionadded:: 3.2
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def register(name: str, func: Callable) -> None:
|
|
114
|
+
_REGISTRY[name] = func
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def list_all() -> list[str]:
|
|
118
|
+
return list_all()
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def get(name: str) -> Any:
|
|
122
|
+
return get(name)
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def has(name: str) -> bool:
|
|
126
|
+
return has(name)
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def remove(name: str) -> None:
|
|
130
|
+
remove(name)
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def clear() -> None:
|
|
134
|
+
clear()
|
|
@@ -8,7 +8,7 @@ def _get_version_tuple(version: str) -> tuple[int, int, int]:
|
|
|
8
8
|
return (major, minor, patch)
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
__version__: str = "3.
|
|
11
|
+
__version__: str = "3.4.0"
|
|
12
12
|
__version_tuple__: tuple[int, int, int] = _get_version_tuple(__version__)
|
|
13
13
|
__commit_id__: str | None = None
|
|
14
14
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
errortools - a toolset for working with Python exceptions and warnings and logging.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import sys
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
from _errortools.raises import raises, assert_raises, raises_all, reraise
|
|
@@ -18,7 +19,7 @@ from _errortools.errno import (
|
|
|
18
19
|
get_all_errno_codes,
|
|
19
20
|
is_valid_errno,
|
|
20
21
|
)
|
|
21
|
-
from _errortools.classes.group import BaseGroup, GroupErrors
|
|
22
|
+
from _errortools.classes.group import BaseGroup, GroupErrors # noqa: F401
|
|
22
23
|
from _errortools.decorator.cache import error_cache
|
|
23
24
|
from _errortools.decorator.deprecated import deprecated, experimental
|
|
24
25
|
from _errortools.decorator.handlers import suppress, convert
|
|
@@ -47,7 +48,7 @@ from _errortools.classes.abc import (
|
|
|
47
48
|
Raiseable,
|
|
48
49
|
Error,
|
|
49
50
|
)
|
|
50
|
-
from _errortools.classes.protocol import (
|
|
51
|
+
from _errortools.classes.protocol import ( # noqa: F401
|
|
51
52
|
ExceptionLike,
|
|
52
53
|
ExceptionGroupLike,
|
|
53
54
|
BaseExceptionGroupLike,
|
|
@@ -73,7 +74,7 @@ from _errortools.typing import (
|
|
|
73
74
|
TracebackType,
|
|
74
75
|
FrameType,
|
|
75
76
|
)
|
|
76
|
-
from _errortools.plugins import run, register, list_all, get, remove, Registry
|
|
77
|
+
from _errortools.plugins import run, register, list_all, get, has, remove, clear, Registry
|
|
77
78
|
from _errortools.descriptor.errormsg import ErrorMsg
|
|
78
79
|
from _errortools.descriptor.nonblankmsg import NonBlankErrorMsg
|
|
79
80
|
from _errortools.version import (
|
|
@@ -149,6 +150,13 @@ class PluginNamespace:
|
|
|
149
150
|
|
|
150
151
|
plugins = PluginNamespace()
|
|
151
152
|
|
|
153
|
+
_PYTHON_3_11_CAN_USE: list[str] = [
|
|
154
|
+
"GroupErrors",
|
|
155
|
+
"BaseGroup",
|
|
156
|
+
"BaseExceptionGroupLike",
|
|
157
|
+
"ExceptionGroupLike",
|
|
158
|
+
"GroupErrorsLike",
|
|
159
|
+
]
|
|
152
160
|
__all__ = [
|
|
153
161
|
# functions
|
|
154
162
|
"raises",
|
|
@@ -173,8 +181,6 @@ __all__ = [
|
|
|
173
181
|
"TracebackType",
|
|
174
182
|
"FrameType",
|
|
175
183
|
# classes
|
|
176
|
-
"GroupErrors",
|
|
177
|
-
"BaseGroup",
|
|
178
184
|
"BaseErrorCodes",
|
|
179
185
|
"InvalidInputError",
|
|
180
186
|
"NotFoundError",
|
|
@@ -197,8 +203,6 @@ __all__ = [
|
|
|
197
203
|
"ContextException",
|
|
198
204
|
"Error",
|
|
199
205
|
"ExceptionLike",
|
|
200
|
-
"ExceptionGroupLike",
|
|
201
|
-
"BaseExceptionGroupLike",
|
|
202
206
|
"BlockingIOErrorLike",
|
|
203
207
|
"NameErrorLike",
|
|
204
208
|
"StopIterationLike",
|
|
@@ -209,7 +213,6 @@ __all__ = [
|
|
|
209
213
|
"UnicodeEncodeErrorLike",
|
|
210
214
|
"UnicodeTranslateErrorLike",
|
|
211
215
|
"AttributeErrorLike",
|
|
212
|
-
"GroupErrorsLike",
|
|
213
216
|
"ErrortoolsDeprecationWarning",
|
|
214
217
|
# for type hints
|
|
215
218
|
"PureBaseExceptionType",
|
|
@@ -225,9 +228,11 @@ __all__ = [
|
|
|
225
228
|
# plugins
|
|
226
229
|
"register",
|
|
227
230
|
"get",
|
|
231
|
+
"has",
|
|
228
232
|
"list_all",
|
|
229
233
|
"run",
|
|
230
234
|
"remove",
|
|
235
|
+
"clear",
|
|
231
236
|
"Registry",
|
|
232
237
|
# metadata
|
|
233
238
|
"__version__",
|
|
@@ -254,3 +259,6 @@ __all__ = [
|
|
|
254
259
|
]
|
|
255
260
|
|
|
256
261
|
__all__.append("plugins")
|
|
262
|
+
|
|
263
|
+
if sys.version_info >= (3, 11):
|
|
264
|
+
__all__.append(_PYTHON_3_11_CAN_USE)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: errortools
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.4.0
|
|
4
4
|
Summary: errortools - a toolset for working with Python exceptions and warnings and logging.
|
|
5
5
|
Author-email: Evan Yang <quantbit@126.com>
|
|
6
6
|
License: Copyright (c) 2026 authors see AUTHORS.txt
|
|
@@ -41,7 +41,6 @@ _errortools/logging/sink.py
|
|
|
41
41
|
_errortools/wrappers/__init__.py
|
|
42
42
|
_errortools/wrappers/cache.py
|
|
43
43
|
_errortools/wrappers/ignore.py
|
|
44
|
-
docs/conf.py
|
|
45
44
|
errortools/__init__.py
|
|
46
45
|
errortools/__main__.py
|
|
47
46
|
errortools/future.py
|
|
@@ -57,22 +56,15 @@ testing/__init__.py
|
|
|
57
56
|
testing/__main__.py
|
|
58
57
|
testing/conftest.py
|
|
59
58
|
testing/run_tests.py
|
|
60
|
-
testing/test_abc.py
|
|
61
59
|
testing/test_decorator.py
|
|
62
60
|
testing/test_descriptor.py
|
|
63
61
|
testing/test_errno.py
|
|
64
|
-
testing/test_errorcodes.py
|
|
65
|
-
testing/test_future.py
|
|
66
62
|
testing/test_groups.py
|
|
67
63
|
testing/test_ignore.py
|
|
68
|
-
testing/test_logging.py
|
|
69
|
-
testing/test_partials.py
|
|
70
64
|
testing/test_plugins.py
|
|
71
|
-
testing/test_protocols.py
|
|
72
65
|
testing/test_raises.py
|
|
73
66
|
testing/test_typing.py
|
|
74
67
|
testing/test_version.py
|
|
75
|
-
testing/test_warnings.py
|
|
76
68
|
testing/benchmark/__init__.py
|
|
77
69
|
testing/benchmark/test_future_perf.py
|
|
78
70
|
testing/test_testing/__init__.py
|
|
@@ -1,105 +1,200 @@
|
|
|
1
|
-
"""Tests for _errortools/plugins — ultra-lightweight plugin system."""
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
|
-
from _errortools.plugins import (
|
|
6
|
-
register,
|
|
7
|
-
get,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# =============================================================================
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
assert "
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@register("
|
|
102
|
-
def
|
|
103
|
-
pass
|
|
104
|
-
|
|
105
|
-
|
|
1
|
+
"""Tests for _errortools/plugins — ultra-lightweight plugin system."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from _errortools.plugins import (
|
|
6
|
+
register,
|
|
7
|
+
get,
|
|
8
|
+
has,
|
|
9
|
+
run,
|
|
10
|
+
list_all,
|
|
11
|
+
remove,
|
|
12
|
+
clear,
|
|
13
|
+
Registry,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# =============================================================================
|
|
17
|
+
# register & run
|
|
18
|
+
# =============================================================================
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestPluginRegisterAndRun:
|
|
22
|
+
def test_register_decorator(self):
|
|
23
|
+
@register("test_func")
|
|
24
|
+
def f():
|
|
25
|
+
return "ok"
|
|
26
|
+
|
|
27
|
+
assert run("test_func") == "ok"
|
|
28
|
+
|
|
29
|
+
def test_run_raises_when_not_registered(self):
|
|
30
|
+
with pytest.raises(ValueError, match="Plugin 'nonexistent' is not registered"):
|
|
31
|
+
run("nonexistent")
|
|
32
|
+
|
|
33
|
+
def test_plugin_with_args_kwargs(self):
|
|
34
|
+
@register("add")
|
|
35
|
+
def add(a, b):
|
|
36
|
+
return a + b
|
|
37
|
+
|
|
38
|
+
assert run("add", 2, 3) == 5
|
|
39
|
+
assert run("add", a=10, b=20) == 30
|
|
40
|
+
|
|
41
|
+
def test_get_returns_correct_function(self):
|
|
42
|
+
@register("myfunc")
|
|
43
|
+
def f():
|
|
44
|
+
return "test"
|
|
45
|
+
|
|
46
|
+
assert get("myfunc") is f
|
|
47
|
+
|
|
48
|
+
def test_get_with_default(self):
|
|
49
|
+
assert get("missing", default=42) == 42
|
|
50
|
+
|
|
51
|
+
def test_get_with_none_default(self):
|
|
52
|
+
assert get("missing", default=None) is None
|
|
53
|
+
|
|
54
|
+
def test_register_overwrites(self):
|
|
55
|
+
@register("overwrite_me")
|
|
56
|
+
def first():
|
|
57
|
+
return "first"
|
|
58
|
+
|
|
59
|
+
@register("overwrite_me")
|
|
60
|
+
def second():
|
|
61
|
+
return "second"
|
|
62
|
+
|
|
63
|
+
assert run("overwrite_me") == "second"
|
|
64
|
+
|
|
65
|
+
def test_run_forwards_all_args(self):
|
|
66
|
+
@register("variadic")
|
|
67
|
+
def variadic(*args, **kwargs):
|
|
68
|
+
return (args, kwargs)
|
|
69
|
+
|
|
70
|
+
assert run("variadic", 1, 2, x=3) == ((1, 2), {"x": 3})
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# =============================================================================
|
|
74
|
+
# has
|
|
75
|
+
# =============================================================================
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestPluginHas:
|
|
79
|
+
def test_has_true(self):
|
|
80
|
+
@register("exists")
|
|
81
|
+
def f():
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
assert has("exists") is True
|
|
85
|
+
|
|
86
|
+
def test_has_false(self):
|
|
87
|
+
assert has("never_registered") is False
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# =============================================================================
|
|
91
|
+
# list_all
|
|
92
|
+
# =============================================================================
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TestPluginList:
|
|
96
|
+
def test_list_all_includes_registered_plugins(self):
|
|
97
|
+
@register("plugin1")
|
|
98
|
+
def f1():
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
@register("plugin2")
|
|
102
|
+
def f2():
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
plugins = list_all()
|
|
106
|
+
assert "plugin1" in plugins
|
|
107
|
+
assert "plugin2" in plugins
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# =============================================================================
|
|
111
|
+
# remove
|
|
112
|
+
# =============================================================================
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class TestPluginRemove:
|
|
116
|
+
def test_remove_existing_plugin(self):
|
|
117
|
+
@register("toremove")
|
|
118
|
+
def f():
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
remove("toremove")
|
|
122
|
+
with pytest.raises(ValueError):
|
|
123
|
+
get("toremove")
|
|
124
|
+
|
|
125
|
+
def test_remove_nonexistent_is_safe(self):
|
|
126
|
+
# Should not raise
|
|
127
|
+
remove("never_existed")
|
|
128
|
+
|
|
129
|
+
def test_remove_returns_none(self):
|
|
130
|
+
@register("for_remove")
|
|
131
|
+
def f():
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
assert remove("for_remove") is None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# =============================================================================
|
|
138
|
+
# clear
|
|
139
|
+
# =============================================================================
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class TestPluginClear:
|
|
143
|
+
def test_clear_removes_all(self):
|
|
144
|
+
@register("a")
|
|
145
|
+
def a():
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
@register("b")
|
|
149
|
+
def b():
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
clear()
|
|
153
|
+
assert list_all() == []
|
|
154
|
+
assert has("a") is False
|
|
155
|
+
assert has("b") is False
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# =============================================================================
|
|
159
|
+
# Registry class (static)
|
|
160
|
+
# =============================================================================
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class TestRegistryClass:
|
|
164
|
+
def test_static_register_and_get(self):
|
|
165
|
+
def my_static_func():
|
|
166
|
+
return "static"
|
|
167
|
+
|
|
168
|
+
Registry.register("static_plugin", my_static_func)
|
|
169
|
+
assert Registry.get("static_plugin") is my_static_func
|
|
170
|
+
|
|
171
|
+
def test_static_list_all(self):
|
|
172
|
+
@register("listme")
|
|
173
|
+
def f():
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
assert "listme" in Registry.list_all()
|
|
177
|
+
|
|
178
|
+
def test_registry_get_raises_when_missing(self):
|
|
179
|
+
with pytest.raises(ValueError, match="Plugin 'missing' is not registered"):
|
|
180
|
+
Registry.get("missing")
|
|
181
|
+
|
|
182
|
+
def test_registry_register_overwrites(self):
|
|
183
|
+
def first():
|
|
184
|
+
return "first"
|
|
185
|
+
|
|
186
|
+
def second():
|
|
187
|
+
return "second"
|
|
188
|
+
|
|
189
|
+
Registry.register("overwrite_registry", first)
|
|
190
|
+
assert Registry.get("overwrite_registry") is first
|
|
191
|
+
Registry.register("overwrite_registry", second)
|
|
192
|
+
assert Registry.get("overwrite_registry") is second
|
|
193
|
+
|
|
194
|
+
def test_registry_remove(self):
|
|
195
|
+
@register("registry_remove_me")
|
|
196
|
+
def f():
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
Registry.remove("registry_remove_me")
|
|
200
|
+
assert has("registry_remove_me") is False
|