1from __future__ import annotations
2
3import functools
4import operator
5
6
7# from jaraco.context 6.1
8class ExceptionTrap:
9 """
10 A context manager that will catch certain exceptions and provide an
11 indication they occurred.
12
13 >>> with ExceptionTrap() as trap:
14 ... raise Exception()
15 >>> bool(trap)
16 True
17
18 >>> with ExceptionTrap() as trap:
19 ... pass
20 >>> bool(trap)
21 False
22
23 >>> with ExceptionTrap(ValueError) as trap:
24 ... raise ValueError("1 + 1 is not 3")
25 >>> bool(trap)
26 True
27 >>> trap.value
28 ValueError('1 + 1 is not 3')
29 >>> trap.tb
30 <traceback object at ...>
31
32 >>> with ExceptionTrap(ValueError) as trap:
33 ... raise Exception()
34 Traceback (most recent call last):
35 ...
36 Exception
37
38 >>> bool(trap)
39 False
40 """
41
42 exc_info = None, None, None
43
44 def __init__(self, exceptions=(Exception,)):
45 self.exceptions = exceptions
46
47 def __enter__(self):
48 return self
49
50 @property
51 def type(self):
52 return self.exc_info[0]
53
54 @property
55 def value(self):
56 return self.exc_info[1]
57
58 @property
59 def tb(self):
60 return self.exc_info[2]
61
62 def __exit__(self, *exc_info):
63 type = exc_info[0]
64 matches = type and issubclass(type, self.exceptions)
65 if matches:
66 self.exc_info = exc_info
67 return matches
68
69 def __bool__(self):
70 return bool(self.type)
71
72 def raises(self, func, *, _test=bool):
73 """
74 Wrap func and replace the result with the truth
75 value of the trap (True if an exception occurred).
76
77 First, give the decorator an alias to support Python 3.8
78 Syntax.
79
80 >>> raises = ExceptionTrap(ValueError).raises
81
82 Now decorate a function that always fails.
83
84 >>> @raises
85 ... def fail():
86 ... raise ValueError('failed')
87 >>> fail()
88 True
89 """
90
91 @functools.wraps(func)
92 def wrapper(*args, **kwargs):
93 with ExceptionTrap(self.exceptions) as trap:
94 func(*args, **kwargs)
95 return _test(trap)
96
97 return wrapper
98
99 def passes(self, func):
100 """
101 Wrap func and replace the result with the truth
102 value of the trap (True if no exception).
103
104 First, give the decorator an alias to support Python 3.8
105 Syntax.
106
107 >>> passes = ExceptionTrap(ValueError).passes
108
109 Now decorate a function that always fails.
110
111 >>> @passes
112 ... def fail():
113 ... raise ValueError('failed')
114
115 >>> fail()
116 False
117 """
118 return self.raises(func, _test=operator.not_)