errortools 2.2.0__tar.gz → 2.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-2.2.0/errortools.egg-info → errortools-2.3.0}/PKG-INFO +2 -1
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/_cli.py +33 -32
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/classes/abc.py +3 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/cli.py +42 -54
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/decorator/deprecated.py +28 -0
- errortools-2.3.0/_errortools/errno.py +78 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/ignore.py +23 -19
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/typing.py +4 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/version.py +2 -2
- {errortools-2.2.0 → errortools-2.3.0}/errortools/__init__.py +14 -1
- {errortools-2.2.0 → errortools-2.3.0/errortools.egg-info}/PKG-INFO +2 -1
- {errortools-2.2.0 → errortools-2.3.0}/errortools.egg-info/SOURCES.txt +2 -1
- {errortools-2.2.0 → errortools-2.3.0}/errortools.egg-info/entry_points.txt +1 -1
- errortools-2.3.0/errortools.egg-info/requires.txt +2 -0
- {errortools-2.2.0 → errortools-2.3.0}/setup.py +3 -3
- {errortools-2.2.0 → errortools-2.3.0}/tests/__init__.py +1 -1
- errortools-2.2.0/tests/test_cache.py → errortools-2.3.0/tests/test_decorator.py +153 -2
- errortools-2.3.0/tests/test_errno.py +112 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_typing.py +9 -0
- errortools-2.2.0/errortools.egg-info/requires.txt +0 -1
- errortools-2.2.0/tests/test_decorator.py +0 -85
- {errortools-2.2.0 → errortools-2.3.0}/AUTHORS.txt +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/LICENSE.txt +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/README.md +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/__init__.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/classes/__init__.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/classes/errorcodes.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/classes/group.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/classes/warn.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/const.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/decorator/__init__.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/decorator/cache.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/descriptor/__init__.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/descriptor/errormsg.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/descriptor/nonblankmsg.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/future.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/logging/__init__.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/logging/base.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/logging/level.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/logging/logger.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/logging/record.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/logging/sink.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/metadata.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/methods/__init__.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/methods/errorattr.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/methods/errordelattr.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/methods/errorhasattr.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/methods/errorsetattr.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/partial.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/py.typed +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/raises.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/wrappers/__init__.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/wrappers/cache.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/_errortools/wrappers/ignore.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/errortools/__main__.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/errortools.egg-info/dependency_links.txt +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/errortools.egg-info/top_level.txt +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/setup.cfg +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/conftest.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/run_tests.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_abc.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_const.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_descriptor.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_errorcodes.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_groups.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_ignore.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_logging.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_mixins.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_partials.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_raises.py +0 -0
- {errortools-2.2.0 → errortools-2.3.0}/tests/test_warnings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: errortools
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -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.
|
|
22
|
-
log.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
print(f"
|
|
29
|
-
print(f"
|
|
30
|
-
print(f"
|
|
31
|
-
print(f"
|
|
32
|
-
print(f"
|
|
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=
|
|
52
|
+
parser = argparse.ArgumentParser(description=desc, prog=prog, color=True)
|
|
24
53
|
else:
|
|
25
|
-
parser = argparse.ArgumentParser(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
|
|
84
|
-
"""Logging main CLI entry point."""
|
|
78
|
+
def main() -> None:
|
|
85
79
|
args = parse_args(sys.argv[1:])
|
|
86
80
|
|
|
87
|
-
if
|
|
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()
|
|
@@ -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,78 @@
|
|
|
1
|
+
import errno
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_errno_name(code: int) -> str | None:
|
|
5
|
+
"""Get the symbolic name for an errno code.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
code: The errno code (e.g., 2 for ENOENT)
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
The errno name (e.g., "ENOENT") or None if not found
|
|
12
|
+
"""
|
|
13
|
+
for name in dir(errno):
|
|
14
|
+
if name.isupper() and getattr(errno, name) == code:
|
|
15
|
+
return name
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_errno_message(code: int) -> str:
|
|
20
|
+
"""Get the error message for an errno code.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
code: The errno code
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
The error message string
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
return errno.errorcode.get(code, f"Unknown error {code}")
|
|
30
|
+
except (AttributeError, KeyError):
|
|
31
|
+
return f"Unknown error {code}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_all_errno_codes() -> dict[str, int]:
|
|
35
|
+
"""Get a mapping of all errno names to their numeric codes.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dictionary mapping errno names to their numeric values
|
|
39
|
+
"""
|
|
40
|
+
codes = {}
|
|
41
|
+
for name in dir(errno):
|
|
42
|
+
if name.isupper():
|
|
43
|
+
try:
|
|
44
|
+
value = getattr(errno, name)
|
|
45
|
+
if isinstance(value, int):
|
|
46
|
+
codes[name] = value
|
|
47
|
+
except (AttributeError, TypeError):
|
|
48
|
+
pass
|
|
49
|
+
return codes
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def is_valid_errno(code: int) -> bool:
|
|
53
|
+
"""Check if a code is a valid errno constant.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
code: The code to check
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
True if the code is a valid errno, False otherwise
|
|
60
|
+
"""
|
|
61
|
+
return get_errno_name(code) is not None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def strerror(code: int) -> str:
|
|
65
|
+
"""Get human-readable error message for errno code.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
code: The errno code
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Error message string
|
|
72
|
+
"""
|
|
73
|
+
import os
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
return os.strerror(code)
|
|
77
|
+
except (ValueError, OSError):
|
|
78
|
+
return f"Unknown error {code}"
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from collections.abc import Iterator, Callable
|
|
6
6
|
from contextlib import contextmanager
|
|
7
7
|
from functools import wraps
|
|
8
|
+
from typing import Any, TypeVar
|
|
8
9
|
import asyncio
|
|
9
10
|
import inspect
|
|
10
11
|
import time
|
|
@@ -21,6 +22,9 @@ __all__ = [
|
|
|
21
22
|
"retry",
|
|
22
23
|
]
|
|
23
24
|
|
|
25
|
+
Func = TypeVar("Func", bound=Callable[..., Any])
|
|
26
|
+
ExceptionType = type[BaseException]
|
|
27
|
+
ExceptionTypes = tuple[ExceptionType, ...]
|
|
24
28
|
|
|
25
29
|
# A Context Manager? Maybe it is...
|
|
26
30
|
|
|
@@ -107,7 +111,7 @@ class fast_ignore:
|
|
|
107
111
|
|
|
108
112
|
__slots__ = ("_excs",)
|
|
109
113
|
|
|
110
|
-
def __init__(self, *excs:
|
|
114
|
+
def __init__(self, *excs: ExceptionType) -> None:
|
|
111
115
|
for exc in excs:
|
|
112
116
|
if not isinstance(exc, type) or not issubclass(exc, BaseException):
|
|
113
117
|
raise TypeError(f"Expected Exception subclass, got {exc!r}")
|
|
@@ -116,14 +120,14 @@ class fast_ignore:
|
|
|
116
120
|
def __enter__(self) -> None:
|
|
117
121
|
return
|
|
118
122
|
|
|
119
|
-
def __exit__(self, typ:
|
|
123
|
+
def __exit__(self, typ: ExceptionType | None, _, __) -> bool:
|
|
120
124
|
if typ is None:
|
|
121
125
|
return False
|
|
122
126
|
return typ in self._excs
|
|
123
127
|
|
|
124
128
|
|
|
125
129
|
@contextmanager
|
|
126
|
-
def ignore_subclass(base:
|
|
130
|
+
def ignore_subclass(base: ExceptionType) -> Iterator[None]:
|
|
127
131
|
"""Context manager that suppresses any exception whose type is a subclass of *base*.
|
|
128
132
|
|
|
129
133
|
Args:
|
|
@@ -227,13 +231,13 @@ class retry:
|
|
|
227
231
|
>>> unstable()
|
|
228
232
|
Traceback (most recent call last):
|
|
229
233
|
...
|
|
230
|
-
|
|
234
|
+
ValueError: oops
|
|
231
235
|
"""
|
|
232
236
|
|
|
233
237
|
def __init__(
|
|
234
238
|
self,
|
|
235
239
|
times: int,
|
|
236
|
-
on:
|
|
240
|
+
on: ExceptionType | ExceptionTypes = Exception,
|
|
237
241
|
delay: float = 0,
|
|
238
242
|
) -> None:
|
|
239
243
|
if times < 0:
|
|
@@ -241,23 +245,19 @@ class retry:
|
|
|
241
245
|
|
|
242
246
|
exc_types = on if isinstance(on, tuple) else (on,)
|
|
243
247
|
for t in exc_types:
|
|
244
|
-
if not isinstance(t, type) or not issubclass(t,
|
|
248
|
+
if not isinstance(t, type) or not issubclass(t, BaseException):
|
|
245
249
|
raise TypeError(f"Expected Exception subclass, got {t!r}")
|
|
246
250
|
|
|
247
251
|
self._times = times
|
|
248
252
|
self._on = exc_types
|
|
249
253
|
self._delay = delay
|
|
250
254
|
|
|
251
|
-
|
|
252
|
-
# Decorator protocol
|
|
253
|
-
# ------------------------------------------------------------------
|
|
254
|
-
|
|
255
|
-
def __call__(self, func: Callable) -> Callable:
|
|
255
|
+
def __call__(self, func: Func) -> Func:
|
|
256
256
|
if inspect.iscoroutinefunction(func):
|
|
257
257
|
|
|
258
258
|
@wraps(func)
|
|
259
|
-
async def async_wrapper(*args, **kwargs):
|
|
260
|
-
last_exc:
|
|
259
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
260
|
+
last_exc: BaseException | None = None
|
|
261
261
|
for attempt in range(self._times + 1):
|
|
262
262
|
try:
|
|
263
263
|
return await func(*args, **kwargs)
|
|
@@ -265,13 +265,15 @@ class retry:
|
|
|
265
265
|
last_exc = exc
|
|
266
266
|
if attempt < self._times and self._delay:
|
|
267
267
|
await asyncio.sleep(self._delay)
|
|
268
|
-
|
|
268
|
+
if last_exc is not None:
|
|
269
|
+
raise last_exc
|
|
270
|
+
raise RuntimeError("No exception to raise")
|
|
269
271
|
|
|
270
|
-
return async_wrapper
|
|
272
|
+
return async_wrapper # type: ignore
|
|
271
273
|
|
|
272
274
|
@wraps(func)
|
|
273
|
-
def wrapper(*args, **kwargs):
|
|
274
|
-
last_exc:
|
|
275
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
276
|
+
last_exc: BaseException | None = None
|
|
275
277
|
for attempt in range(self._times + 1):
|
|
276
278
|
try:
|
|
277
279
|
return func(*args, **kwargs)
|
|
@@ -279,6 +281,8 @@ class retry:
|
|
|
279
281
|
last_exc = exc
|
|
280
282
|
if attempt < self._times and self._delay:
|
|
281
283
|
time.sleep(self._delay)
|
|
282
|
-
|
|
284
|
+
if last_exc is not None:
|
|
285
|
+
raise last_exc
|
|
286
|
+
raise RuntimeError("No exception to raise")
|
|
283
287
|
|
|
284
|
-
return wrapper
|
|
288
|
+
return wrapper # type: ignore
|
|
@@ -24,6 +24,7 @@ __all__ = [
|
|
|
24
24
|
"LookupError_",
|
|
25
25
|
"RuntimeError_",
|
|
26
26
|
"ExceptionType",
|
|
27
|
+
"WarningType",
|
|
27
28
|
"TracebackType",
|
|
28
29
|
"FrameType",
|
|
29
30
|
]
|
|
@@ -74,6 +75,9 @@ RuntimeError_: TypeAlias = Union[RuntimeFailure, TimeoutFailure]
|
|
|
74
75
|
ExceptionType: TypeAlias = type[Exception]
|
|
75
76
|
"""Type alias for an exception *class* (not an instance)."""
|
|
76
77
|
|
|
78
|
+
WarningType: TypeAlias = type[Warning]
|
|
79
|
+
"""Type alias for an warning *class* like `ExceptionType` (not an instance)."""
|
|
80
|
+
|
|
77
81
|
# ---------------------------------------------------------------------------
|
|
78
82
|
# Types from ``types`` module
|
|
79
83
|
# ---------------------------------------------------------------------------
|
|
@@ -11,9 +11,15 @@ from _errortools.ignore import (
|
|
|
11
11
|
timeout,
|
|
12
12
|
retry,
|
|
13
13
|
)
|
|
14
|
+
from _errortools.errno import (
|
|
15
|
+
get_errno_message,
|
|
16
|
+
get_errno_name,
|
|
17
|
+
get_all_errno_codes,
|
|
18
|
+
is_valid_errno,
|
|
19
|
+
)
|
|
14
20
|
from _errortools.classes.group import BaseGroup, GroupErrors
|
|
15
21
|
from _errortools.decorator.cache import error_cache
|
|
16
|
-
from _errortools.decorator.deprecated import deprecated
|
|
22
|
+
from _errortools.decorator.deprecated import deprecated, experimental
|
|
17
23
|
from _errortools.classes.errorcodes import (
|
|
18
24
|
PureBaseException,
|
|
19
25
|
ContextException,
|
|
@@ -56,6 +62,7 @@ from _errortools.typing import (
|
|
|
56
62
|
InputError,
|
|
57
63
|
AccessError,
|
|
58
64
|
ExceptionType,
|
|
65
|
+
WarningType,
|
|
59
66
|
TracebackType,
|
|
60
67
|
FrameType,
|
|
61
68
|
)
|
|
@@ -98,7 +105,12 @@ __all__ = [
|
|
|
98
105
|
"ignore_warns",
|
|
99
106
|
"timeout",
|
|
100
107
|
"retry",
|
|
108
|
+
"get_errno_message",
|
|
109
|
+
"get_errno_name",
|
|
110
|
+
"get_all_errno_codes",
|
|
111
|
+
"is_valid_errno",
|
|
101
112
|
"deprecated",
|
|
113
|
+
"experimental",
|
|
102
114
|
"error_cache",
|
|
103
115
|
"TracebackType",
|
|
104
116
|
"FrameType",
|
|
@@ -141,6 +153,7 @@ __all__ = [
|
|
|
141
153
|
"LookupError_",
|
|
142
154
|
"RuntimeError_",
|
|
143
155
|
"ExceptionType",
|
|
156
|
+
"WarningType",
|
|
144
157
|
# metadata
|
|
145
158
|
"__version__",
|
|
146
159
|
"__version_tuple__",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: errortools
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.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
|
|
@@ -6,6 +6,7 @@ _errortools/__init__.py
|
|
|
6
6
|
_errortools/_cli.py
|
|
7
7
|
_errortools/cli.py
|
|
8
8
|
_errortools/const.py
|
|
9
|
+
_errortools/errno.py
|
|
9
10
|
_errortools/future.py
|
|
10
11
|
_errortools/ignore.py
|
|
11
12
|
_errortools/metadata.py
|
|
@@ -51,10 +52,10 @@ tests/__init__.py
|
|
|
51
52
|
tests/conftest.py
|
|
52
53
|
tests/run_tests.py
|
|
53
54
|
tests/test_abc.py
|
|
54
|
-
tests/test_cache.py
|
|
55
55
|
tests/test_const.py
|
|
56
56
|
tests/test_decorator.py
|
|
57
57
|
tests/test_descriptor.py
|
|
58
|
+
tests/test_errno.py
|
|
58
59
|
tests/test_errorcodes.py
|
|
59
60
|
tests/test_groups.py
|
|
60
61
|
tests/test_ignore.py
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="errortools",
|
|
5
|
-
version="2.
|
|
5
|
+
version="2.3.0",
|
|
6
6
|
description="errortools - a toolset for working with Python exceptions and warnings and logging.",
|
|
7
7
|
long_description=open("README.md", encoding="utf-8").read(),
|
|
8
8
|
long_description_content_type="text/markdown",
|
|
@@ -25,11 +25,11 @@ setup(
|
|
|
25
25
|
package_data={"errortools": ["py.typed"]},
|
|
26
26
|
include_package_data=True,
|
|
27
27
|
python_requires=">=3.10",
|
|
28
|
-
install_requires=["namebyauthor==1.0.0"],
|
|
28
|
+
install_requires=["namebyauthor==1.0.0", "typing_extensions>=4.8.0"],
|
|
29
29
|
entry_points={
|
|
30
30
|
"console_scripts": [
|
|
31
31
|
"python -m errortools = _errortools.cli:main",
|
|
32
|
-
"logger = _errortools.cli:
|
|
32
|
+
"logger = _errortools.cli:main",
|
|
33
33
|
]
|
|
34
34
|
},
|
|
35
35
|
)
|
|
@@ -1,14 +1,165 @@
|
|
|
1
|
-
"""Tests for _errortools/
|
|
1
|
+
"""Tests for _errortools/decorator — decorators."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
2
4
|
|
|
3
5
|
import pytest
|
|
4
6
|
|
|
5
7
|
from _errortools.decorator.cache import error_cache
|
|
8
|
+
from _errortools.decorator.deprecated import deprecated, experimental
|
|
6
9
|
from . import HAS_PYTEST
|
|
7
10
|
|
|
8
11
|
if not HAS_PYTEST:
|
|
9
|
-
print("pytest is required to run these tests, skip run
|
|
12
|
+
print("pytest is required to run these tests, skip run test_decorator.py")
|
|
10
13
|
exit(0)
|
|
11
14
|
|
|
15
|
+
# =============================================================================
|
|
16
|
+
# deprecated decorator
|
|
17
|
+
# =============================================================================
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestDeprecatedDecorator:
|
|
21
|
+
def test_bare_decorator_with_version(self):
|
|
22
|
+
@deprecated(version="2.0")
|
|
23
|
+
def f(x):
|
|
24
|
+
return x
|
|
25
|
+
|
|
26
|
+
with warnings.catch_warnings():
|
|
27
|
+
warnings.simplefilter("ignore")
|
|
28
|
+
assert f(3) == 3
|
|
29
|
+
|
|
30
|
+
def test_decorator_with_reason(self):
|
|
31
|
+
@deprecated(version="2.0", reason="Use new_func instead")
|
|
32
|
+
def f(x):
|
|
33
|
+
return x * 2
|
|
34
|
+
|
|
35
|
+
with warnings.catch_warnings():
|
|
36
|
+
warnings.simplefilter("ignore")
|
|
37
|
+
assert f(5) == 10
|
|
38
|
+
|
|
39
|
+
def test_wrapper_has_correct_name(self):
|
|
40
|
+
@deprecated(version="2.0")
|
|
41
|
+
def my_func(x):
|
|
42
|
+
return x
|
|
43
|
+
|
|
44
|
+
assert my_func.__name__ == "my_func"
|
|
45
|
+
|
|
46
|
+
def test_wrapped_attribute(self):
|
|
47
|
+
def inner(x):
|
|
48
|
+
return x
|
|
49
|
+
|
|
50
|
+
wrapped = deprecated(version="2.0")(inner)
|
|
51
|
+
assert wrapped.__wrapped__ is inner
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TestDeprecatedWarning:
|
|
55
|
+
def test_emits_deprecation_warning_on_call(self):
|
|
56
|
+
@deprecated(version="2.0")
|
|
57
|
+
def f():
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
with pytest.warns(DeprecationWarning) as record:
|
|
61
|
+
f()
|
|
62
|
+
|
|
63
|
+
assert len(record) == 1
|
|
64
|
+
warn = record[0]
|
|
65
|
+
assert "deprecated since version 2.0" in str(warn.message)
|
|
66
|
+
|
|
67
|
+
def test_warning_contains_reason(self):
|
|
68
|
+
@deprecated(version="2.0", reason="Please upgrade API")
|
|
69
|
+
def f():
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
with pytest.warns(DeprecationWarning) as record:
|
|
73
|
+
f()
|
|
74
|
+
|
|
75
|
+
assert "Please upgrade API" in str(record[0].message)
|
|
76
|
+
|
|
77
|
+
def test_warning_stacklevel_correct(self):
|
|
78
|
+
@deprecated(version="2.0")
|
|
79
|
+
def f():
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
with pytest.warns(DeprecationWarning) as record:
|
|
83
|
+
f()
|
|
84
|
+
|
|
85
|
+
# Ensure warning points to caller, not wrapper
|
|
86
|
+
assert record[0].lineno is not None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# =============================================================================
|
|
90
|
+
# experimental decorator
|
|
91
|
+
# =============================================================================
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class TestExperimentalDecorator:
|
|
95
|
+
def test_bare_decorator(self):
|
|
96
|
+
@experimental()
|
|
97
|
+
def f(x):
|
|
98
|
+
return x
|
|
99
|
+
|
|
100
|
+
with warnings.catch_warnings():
|
|
101
|
+
warnings.simplefilter("ignore")
|
|
102
|
+
assert f(3) == 3
|
|
103
|
+
|
|
104
|
+
def test_decorator_with_reason(self):
|
|
105
|
+
@experimental(reason="API may change without notice")
|
|
106
|
+
def f(x):
|
|
107
|
+
return x * 2
|
|
108
|
+
|
|
109
|
+
with warnings.catch_warnings():
|
|
110
|
+
warnings.simplefilter("ignore")
|
|
111
|
+
assert f(5) == 10
|
|
112
|
+
|
|
113
|
+
def test_wrapper_has_correct_name(self):
|
|
114
|
+
@experimental()
|
|
115
|
+
def my_func(x):
|
|
116
|
+
return x
|
|
117
|
+
|
|
118
|
+
assert my_func.__name__ == "my_func"
|
|
119
|
+
|
|
120
|
+
def test_wrapped_attribute(self):
|
|
121
|
+
def inner(x):
|
|
122
|
+
return x
|
|
123
|
+
|
|
124
|
+
wrapped = experimental()(inner)
|
|
125
|
+
assert wrapped.__wrapped__ is inner
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class TestExperimentalWarning:
|
|
129
|
+
def test_emits_user_warning_on_call(self):
|
|
130
|
+
@experimental()
|
|
131
|
+
def f():
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
with pytest.warns(UserWarning) as record:
|
|
135
|
+
f()
|
|
136
|
+
|
|
137
|
+
assert len(record) == 1
|
|
138
|
+
warn = record[0]
|
|
139
|
+
assert "experimental" in str(warn.message)
|
|
140
|
+
|
|
141
|
+
def test_warning_contains_reason(self):
|
|
142
|
+
@experimental(reason="Subject to change")
|
|
143
|
+
def f():
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
with pytest.warns(UserWarning) as record:
|
|
147
|
+
f()
|
|
148
|
+
|
|
149
|
+
assert "Subject to change" in str(record[0].message)
|
|
150
|
+
|
|
151
|
+
def test_warning_stacklevel_correct(self):
|
|
152
|
+
@experimental()
|
|
153
|
+
def f():
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
with pytest.warns(UserWarning) as record:
|
|
157
|
+
f()
|
|
158
|
+
|
|
159
|
+
# Ensure warning points to caller, not wrapper
|
|
160
|
+
assert record[0].lineno is not None
|
|
161
|
+
|
|
162
|
+
|
|
12
163
|
# =============================================================================
|
|
13
164
|
# error_cache — basic decoration patterns
|
|
14
165
|
# =============================================================================
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Tests for _errortools/errno.py — errno tools."""
|
|
2
|
+
|
|
3
|
+
import errno
|
|
4
|
+
|
|
5
|
+
from _errortools.errno import (
|
|
6
|
+
get_errno_name,
|
|
7
|
+
get_errno_message,
|
|
8
|
+
get_all_errno_codes,
|
|
9
|
+
is_valid_errno,
|
|
10
|
+
strerror,
|
|
11
|
+
)
|
|
12
|
+
from . import HAS_PYTEST
|
|
13
|
+
|
|
14
|
+
if not HAS_PYTEST:
|
|
15
|
+
print("pytest is required to run these tests, skip run test_errno.py")
|
|
16
|
+
exit(0)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestGetErrnoName:
|
|
20
|
+
def test_valid_errno_enoent(self):
|
|
21
|
+
assert get_errno_name(errno.ENOENT) == "ENOENT"
|
|
22
|
+
|
|
23
|
+
def test_valid_errno_eacces(self):
|
|
24
|
+
assert get_errno_name(errno.EACCES) == "EACCES"
|
|
25
|
+
|
|
26
|
+
def test_invalid_errno_code(self):
|
|
27
|
+
assert get_errno_name(9999) is None
|
|
28
|
+
|
|
29
|
+
def test_errno_zero(self):
|
|
30
|
+
assert get_errno_name(0) is None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TestGetErrnoMessage:
|
|
34
|
+
def test_valid_errno_message(self):
|
|
35
|
+
message = get_errno_message(errno.ENOENT)
|
|
36
|
+
assert isinstance(message, str)
|
|
37
|
+
assert len(message) > 0
|
|
38
|
+
|
|
39
|
+
def test_invalid_errno_message(self):
|
|
40
|
+
message = get_errno_message(9999)
|
|
41
|
+
assert "Unknown error" in message or len(message) > 0
|
|
42
|
+
|
|
43
|
+
def test_errno_two_message(self):
|
|
44
|
+
message = get_errno_message(2)
|
|
45
|
+
assert isinstance(message, str)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestGetAllErrnocodes:
|
|
49
|
+
def test_returns_dict(self):
|
|
50
|
+
codes = get_all_errno_codes()
|
|
51
|
+
assert isinstance(codes, dict)
|
|
52
|
+
|
|
53
|
+
def test_contains_enoent(self):
|
|
54
|
+
codes = get_all_errno_codes()
|
|
55
|
+
assert "ENOENT" in codes
|
|
56
|
+
assert codes["ENOENT"] == errno.ENOENT
|
|
57
|
+
|
|
58
|
+
def test_contains_eacces(self):
|
|
59
|
+
codes = get_all_errno_codes()
|
|
60
|
+
assert "EACCES" in codes
|
|
61
|
+
assert codes["EACCES"] == errno.EACCES
|
|
62
|
+
|
|
63
|
+
def test_all_values_are_integers(self):
|
|
64
|
+
codes = get_all_errno_codes()
|
|
65
|
+
for _, code in codes.items():
|
|
66
|
+
assert isinstance(code, int)
|
|
67
|
+
|
|
68
|
+
def test_all_keys_are_uppercase(self):
|
|
69
|
+
codes = get_all_errno_codes()
|
|
70
|
+
for name in codes.keys():
|
|
71
|
+
assert name.isupper()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class TestIsValidErrno:
|
|
75
|
+
def test_valid_errno_enoent(self):
|
|
76
|
+
assert is_valid_errno(errno.ENOENT) is True
|
|
77
|
+
|
|
78
|
+
def test_valid_errno_eacces(self):
|
|
79
|
+
assert is_valid_errno(errno.EACCES) is True
|
|
80
|
+
|
|
81
|
+
def test_invalid_errno(self):
|
|
82
|
+
assert is_valid_errno(9999) is False
|
|
83
|
+
|
|
84
|
+
def test_errno_zero(self):
|
|
85
|
+
assert is_valid_errno(0) is False
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class TestStrerror:
|
|
89
|
+
def test_enoent_message(self):
|
|
90
|
+
message = strerror(errno.ENOENT)
|
|
91
|
+
assert isinstance(message, str)
|
|
92
|
+
assert len(message) > 0
|
|
93
|
+
|
|
94
|
+
def test_eacces_message(self):
|
|
95
|
+
message = strerror(errno.EACCES)
|
|
96
|
+
assert isinstance(message, str)
|
|
97
|
+
assert len(message) > 0
|
|
98
|
+
|
|
99
|
+
def test_invalid_errno_fallback(self):
|
|
100
|
+
message = strerror(9999)
|
|
101
|
+
assert isinstance(message, str)
|
|
102
|
+
assert "Unknown error" in message or len(message) > 0
|
|
103
|
+
|
|
104
|
+
def test_consistency_with_os_strerror(self):
|
|
105
|
+
import os
|
|
106
|
+
|
|
107
|
+
code = errno.ENOENT
|
|
108
|
+
try:
|
|
109
|
+
expected = os.strerror(code)
|
|
110
|
+
assert strerror(code) == expected
|
|
111
|
+
except (ValueError, OSError):
|
|
112
|
+
pass
|
|
@@ -13,6 +13,7 @@ from _errortools.typing import (
|
|
|
13
13
|
LookupError_,
|
|
14
14
|
RuntimeError_,
|
|
15
15
|
ExceptionType,
|
|
16
|
+
WarningType,
|
|
16
17
|
TracebackType,
|
|
17
18
|
FrameType,
|
|
18
19
|
)
|
|
@@ -89,6 +90,13 @@ class TestExceptionTypeAlias:
|
|
|
89
90
|
assert get_args(ExceptionType) == (Exception,)
|
|
90
91
|
|
|
91
92
|
|
|
93
|
+
class TestWarningTypeAlias:
|
|
94
|
+
|
|
95
|
+
def test_warning_type(self):
|
|
96
|
+
assert get_origin(WarningType) is type
|
|
97
|
+
assert get_args(WarningType) == (Warning,)
|
|
98
|
+
|
|
99
|
+
|
|
92
100
|
class TestTracebackAndFrameTypes:
|
|
93
101
|
|
|
94
102
|
def test_traceback_type_matches_real_traceback(self):
|
|
@@ -136,6 +144,7 @@ class TestModuleExports:
|
|
|
136
144
|
"LookupError_",
|
|
137
145
|
"RuntimeError_",
|
|
138
146
|
"ExceptionType",
|
|
147
|
+
"WarningType",
|
|
139
148
|
"TracebackType",
|
|
140
149
|
"FrameType",
|
|
141
150
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
namebyauthor==1.0.0
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"""Tests for _errortools/decorator — decorators."""
|
|
2
|
-
|
|
3
|
-
import warnings
|
|
4
|
-
|
|
5
|
-
import pytest
|
|
6
|
-
|
|
7
|
-
from _errortools.decorator.deprecated import deprecated
|
|
8
|
-
from . import HAS_PYTEST
|
|
9
|
-
|
|
10
|
-
if not HAS_PYTEST:
|
|
11
|
-
print("pytest is required to run these tests, skip run test_decorator.py")
|
|
12
|
-
exit(0)
|
|
13
|
-
|
|
14
|
-
# =============================================================================
|
|
15
|
-
# deprecated decorator
|
|
16
|
-
# =============================================================================
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class TestDeprecatedDecorator:
|
|
20
|
-
def test_bare_decorator_with_version(self):
|
|
21
|
-
@deprecated(version="2.0")
|
|
22
|
-
def f(x):
|
|
23
|
-
return x
|
|
24
|
-
|
|
25
|
-
with warnings.catch_warnings():
|
|
26
|
-
warnings.simplefilter("ignore")
|
|
27
|
-
assert f(3) == 3
|
|
28
|
-
|
|
29
|
-
def test_decorator_with_reason(self):
|
|
30
|
-
@deprecated(version="2.0", reason="Use new_func instead")
|
|
31
|
-
def f(x):
|
|
32
|
-
return x * 2
|
|
33
|
-
|
|
34
|
-
with warnings.catch_warnings():
|
|
35
|
-
warnings.simplefilter("ignore")
|
|
36
|
-
assert f(5) == 10
|
|
37
|
-
|
|
38
|
-
def test_wrapper_has_correct_name(self):
|
|
39
|
-
@deprecated(version="2.0")
|
|
40
|
-
def my_func(x):
|
|
41
|
-
return x
|
|
42
|
-
|
|
43
|
-
assert my_func.__name__ == "my_func"
|
|
44
|
-
|
|
45
|
-
def test_wrapped_attribute(self):
|
|
46
|
-
def inner(x):
|
|
47
|
-
return x
|
|
48
|
-
|
|
49
|
-
wrapped = deprecated(version="2.0")(inner)
|
|
50
|
-
assert wrapped.__wrapped__ is inner
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class TestDeprecatedWarning:
|
|
54
|
-
def test_emits_deprecation_warning_on_call(self):
|
|
55
|
-
@deprecated(version="2.0")
|
|
56
|
-
def f():
|
|
57
|
-
pass
|
|
58
|
-
|
|
59
|
-
with pytest.warns(DeprecationWarning) as record:
|
|
60
|
-
f()
|
|
61
|
-
|
|
62
|
-
assert len(record) == 1
|
|
63
|
-
warn = record[0]
|
|
64
|
-
assert "deprecated since version 2.0" in str(warn.message)
|
|
65
|
-
|
|
66
|
-
def test_warning_contains_reason(self):
|
|
67
|
-
@deprecated(version="2.0", reason="Please upgrade API")
|
|
68
|
-
def f():
|
|
69
|
-
pass
|
|
70
|
-
|
|
71
|
-
with pytest.warns(DeprecationWarning) as record:
|
|
72
|
-
f()
|
|
73
|
-
|
|
74
|
-
assert "Please upgrade API" in str(record[0].message)
|
|
75
|
-
|
|
76
|
-
def test_warning_stacklevel_correct(self):
|
|
77
|
-
@deprecated(version="2.0")
|
|
78
|
-
def f():
|
|
79
|
-
pass
|
|
80
|
-
|
|
81
|
-
with pytest.warns(DeprecationWarning) as record:
|
|
82
|
-
f()
|
|
83
|
-
|
|
84
|
-
# Ensure warning points to caller, not wrapper
|
|
85
|
-
assert record[0].lineno is not None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|