1
- """The typing of RaisesGroup involves a lot of deception and lies, since AFAIK what we
2
- actually want to achieve is ~impossible. This is because we specify what we expect with
3
- instances of RaisesGroup and exception classes, but excinfo.value will be instances of
4
- [Base]ExceptionGroup and instances of exceptions. So we need to "translate" from
5
- RaisesGroup to ExceptionGroup.
6
-
7
- The way it currently works is that RaisesGroup[E] corresponds to
8
- ExceptionInfo[BaseExceptionGroup[E]], so the top-level group will be correct. But
9
- RaisesGroup[RaisesGroup[ValueError]] will become
10
- ExceptionInfo[BaseExceptionGroup[RaisesGroup[ValueError]]]. To get around that we specify
11
- RaisesGroup as a subclass of BaseExceptionGroup during type checking - which should mean
12
- that most static type checking for end users should be mostly correct.
13
- """
14
-
15
1
from __future__ import annotations
16
2
17
3
import sys
18
- from typing import Union
4
+ from typing import Callable , Union
19
5
20
6
from trio .testing import Matcher , RaisesGroup
21
7
from typing_extensions import assert_type
26
12
# split into functions to isolate the different scopes
27
13
28
14
29
- def check_inheritance_and_assignments () -> None :
30
- # Check inheritance
31
- _ : BaseExceptionGroup [ValueError ] = RaisesGroup (ValueError )
32
- _ = RaisesGroup (RaisesGroup (ValueError )) # type: ignore
33
-
34
- a : BaseExceptionGroup [BaseExceptionGroup [ValueError ]]
35
- a = RaisesGroup (RaisesGroup (ValueError ))
36
- a = BaseExceptionGroup ("" , (BaseExceptionGroup ("" , (ValueError (),)),))
37
- assert a
38
-
39
-
40
15
def check_matcher_typevar_default (e : Matcher ) -> object :
41
16
assert e .exception_type is not None
42
17
exc : type [BaseException ] = e .exception_type
@@ -46,29 +21,32 @@ def check_matcher_typevar_default(e: Matcher) -> object:
46
21
47
22
48
23
def check_basic_contextmanager () -> None :
49
- # One level of Group is correctly translated - except it's a BaseExceptionGroup
50
- # instead of an ExceptionGroup.
51
24
with RaisesGroup (ValueError ) as e :
52
25
raise ExceptionGroup ("foo" , (ValueError (),))
53
- assert_type (e .value , BaseExceptionGroup [ValueError ])
26
+ assert_type (e .value , ExceptionGroup [ValueError ])
54
27
55
28
56
29
def check_basic_matches () -> None :
57
30
# check that matches gets rid of the naked ValueError in the union
58
31
exc : ExceptionGroup [ValueError ] | ValueError = ExceptionGroup ("" , (ValueError (),))
59
32
if RaisesGroup (ValueError ).matches (exc ):
60
- assert_type (exc , BaseExceptionGroup [ValueError ])
33
+ assert_type (exc , ExceptionGroup [ValueError ])
34
+
35
+ # also check that BaseExceptionGroup shows up for BaseExceptions
36
+ if RaisesGroup (KeyboardInterrupt ).matches (exc ):
37
+ assert_type (exc , BaseExceptionGroup [KeyboardInterrupt ])
61
38
62
39
63
40
def check_matches_with_different_exception_type () -> None :
64
- # This should probably raise some type error somewhere, since
65
- # ValueError != KeyboardInterrupt
66
41
e : BaseExceptionGroup [KeyboardInterrupt ] = BaseExceptionGroup (
67
42
"" ,
68
43
(KeyboardInterrupt (),),
69
44
)
45
+
46
+ # note: it might be tempting to have this warn.
47
+ # however, that isn't possible with current typing
70
48
if RaisesGroup (ValueError ).matches (e ):
71
- assert_type (e , BaseExceptionGroup [ValueError ])
49
+ assert_type (e , ExceptionGroup [ValueError ])
72
50
73
51
74
52
def check_matcher_init () -> None :
@@ -134,59 +112,44 @@ def handle_value(e: BaseExceptionGroup[ValueError]) -> bool:
134
112
135
113
136
114
def raisesgroup_narrow_baseexceptiongroup () -> None :
137
- """Check type narrowing specifically for the container exceptiongroup.
138
- This is not currently working, and after playing around with it for a bit
139
- I think the only way is to introduce a subclass `NonBaseRaisesGroup`, and overload
140
- `__new__` in Raisesgroup to return the subclass when exceptions are non-base.
141
- (or make current class BaseRaisesGroup and introduce RaisesGroup for non-base)
142
- I encountered problems trying to type this though, see
143
- https://github.com/python/mypy/issues/17251
144
- That is probably possible to work around by entirely using `__new__` instead of
145
- `__init__`, but........ ugh.
146
- """
115
+ """Check type narrowing specifically for the container exceptiongroup."""
147
116
148
117
def handle_group (e : ExceptionGroup [Exception ]) -> bool :
149
118
return True
150
119
151
120
def handle_group_value (e : ExceptionGroup [ValueError ]) -> bool :
152
121
return True
153
122
154
- # should work, but BaseExceptionGroup does not get narrowed to ExceptionGroup
155
- RaisesGroup (ValueError , check = handle_group_value ) # type: ignore
123
+ RaisesGroup (ValueError , check = handle_group_value )
156
124
157
- # should work, but BaseExceptionGroup does not get narrowed to ExceptionGroup
158
- RaisesGroup (Exception , check = handle_group ) # type: ignore
125
+ RaisesGroup (Exception , check = handle_group )
159
126
160
127
161
128
def check_matcher_transparent () -> None :
162
129
with RaisesGroup (Matcher (ValueError )) as e :
163
130
...
164
131
_ : BaseExceptionGroup [ValueError ] = e .value
165
- assert_type (e .value , BaseExceptionGroup [ValueError ])
132
+ assert_type (e .value , ExceptionGroup [ValueError ])
166
133
167
134
168
135
def check_nested_raisesgroups_contextmanager () -> None :
169
136
with RaisesGroup (RaisesGroup (ValueError )) as excinfo :
170
137
raise ExceptionGroup ("foo" , (ValueError (),))
171
138
172
- # thanks to inheritance this assignment works
173
139
_ : BaseExceptionGroup [BaseExceptionGroup [ValueError ]] = excinfo .value
174
- # and it can mostly be treated like an exceptiongroup
175
- print (excinfo .value .exceptions [0 ].exceptions [0 ])
176
140
177
- # but assert_type reveals the lies
178
- print (type (excinfo .value )) # would print "ExceptionGroup"
179
- # typing says it's a BaseExceptionGroup
180
141
assert_type (
181
142
excinfo .value ,
182
- BaseExceptionGroup [ RaisesGroup [ValueError ]],
143
+ ExceptionGroup [ ExceptionGroup [ValueError ]],
183
144
)
184
145
185
- print (type (excinfo .value .exceptions [0 ])) # would print "ExceptionGroup"
186
- # but type checkers are utterly confused
187
146
assert_type (
188
147
excinfo .value .exceptions [0 ],
189
- Union [RaisesGroup [ValueError ], BaseExceptionGroup [RaisesGroup [ValueError ]]],
148
+ # this union is because of how typeshed defines .exceptions
149
+ Union [
150
+ ExceptionGroup [ValueError ],
151
+ ExceptionGroup [ExceptionGroup [ValueError ]],
152
+ ],
190
153
)
191
154
192
155
@@ -196,17 +159,17 @@ def check_nested_raisesgroups_matches() -> None:
196
159
"" ,
197
160
(ExceptionGroup ("" , (ValueError (),)),),
198
161
)
199
- # has the same problems as check_nested_raisesgroups_contextmanager
162
+
200
163
if RaisesGroup (RaisesGroup (ValueError )).matches (exc ):
201
- assert_type (exc , BaseExceptionGroup [ RaisesGroup [ValueError ]])
164
+ assert_type (exc , ExceptionGroup [ ExceptionGroup [ValueError ]])
202
165
203
166
204
167
def check_multiple_exceptions_1 () -> None :
205
168
a = RaisesGroup (ValueError , ValueError )
206
169
b = RaisesGroup (Matcher (ValueError ), Matcher (ValueError ))
207
170
c = RaisesGroup (ValueError , Matcher (ValueError ))
208
171
209
- d : BaseExceptionGroup [ValueError ]
172
+ d : RaisesGroup [ValueError ]
210
173
d = a
211
174
d = b
212
175
d = c
@@ -219,7 +182,7 @@ def check_multiple_exceptions_2() -> None:
219
182
b = RaisesGroup (Matcher (ValueError ), TypeError )
220
183
c = RaisesGroup (ValueError , TypeError )
221
184
222
- d : BaseExceptionGroup [Exception ]
185
+ d : RaisesGroup [Exception ]
223
186
d = a
224
187
d = b
225
188
d = c
@@ -252,3 +215,25 @@ def check_raisesgroup_overloads() -> None:
252
215
253
216
# if they're both false we can of course specify nested raisesgroup
254
217
RaisesGroup (RaisesGroup (ValueError ))
218
+
219
+
220
+ def check_triple_nested_raisesgroup () -> None :
221
+ with RaisesGroup (RaisesGroup (RaisesGroup (ValueError ))) as e :
222
+ assert_type (e .value , ExceptionGroup [ExceptionGroup [ExceptionGroup [ValueError ]]])
223
+
224
+
225
+ def check_check_typing () -> None :
226
+ # mypy issue is https://github.com/python/mypy/issues/18185
227
+
228
+ # fmt: off
229
+ # mypy raises an error on `assert_type`
230
+ # pyright raises an error on `RaisesGroup(ValueError).check`
231
+ # to satisfy both, need to disable formatting and put it on one line
232
+ assert_type (RaisesGroup (ValueError ).check , # type: ignore
233
+ Union [
234
+ Callable [[BaseExceptionGroup [ValueError ]], None ],
235
+ Callable [[ExceptionGroup [ValueError ]], None ],
236
+ None ,
237
+ ],
238
+ )
239
+ # fmt: on
0 commit comments