-
Notifications
You must be signed in to change notification settings - Fork 1
/
exceptions.py
106 lines (81 loc) · 2.75 KB
/
exceptions.py
1
2
3
4
5
6
7
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
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
102
103
104
105
106
from functools import wraps
def raise_(exc):
raise exc
def try_map(f, it, ignore=Exception):
"""
>>> list(try_map(int, '123 456'))
[1, 2, 3, 4, 5, 6]
"""
for obj in it:
try:
yield f(obj)
except ignore:
pass
def unique_exception(name):
"""Create a unique subclass of BaseException.
Usage example:
>>> try:
... raise unique_exception('ayy')('lmao')
... except unique_exception('ayy'):
... print("didn't read lol")
Traceback (most recent call last):
...
exceptions.ayy: lmao
"""
return type(name, (BaseException,), {})
def raises(*allowed, base=Exception, otherwise=None):
"""Document and enforce all exceptions a function might raise.
All other exceptions raised by the decorated functions are
replaced with AssertionErrors, which should
avoids masking the error if
an inappropriate exception type is caught at a higher level.
The decorator is replaced with a zero-overhead passthrough if
assertions are disabled.
Usage example:
>>> @raises(ArithmeticError)
... def f(x):
... return (x ** x) / x
Exceptions of the allowed types raise normally:
>>> f(0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
>>> import sys; f(sys.float_info.max) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
OverflowError: (...)
Others are converted into (mostly) uncatchable types:
>>> try: # doctest: +ELLIPSIS
... f('ayy')
... except Exception:
... pass
Traceback (most recent call last):
...
exceptions.UnexpectedException: TypeError: unsupported...
"""
if not __debug__:
return lambda func: func
else:
for exc_type in allowed:
if not issubclass(exc_type, base) or exc_type == base:
raise ValueError(
f"allowed exceptions must inherit from the given"
f" base class ({base.__name__});"
f" {exc_type.__name__} does not"
)
if otherwise is None:
otherwise = unique_exception('UnexpectedException')
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except allowed:
raise
except base as exc:
msg = type(exc).__name__
if exc.args:
msg = f"{msg}: {exc}"
raise otherwise(msg) from exc
return wrapper
return decorator