-
-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New rule: raising ExceptionGroup child exception loses context/cause #298
Comments
after discussion in gitter and thinking about it we can likely suggest |
oooh, yeah, I'd be happy to have a lint rule for this, it seems like a common mistake in a rare-ish situation and I'd definitely prefer to get the full context when it happens. edit: actually I think you don't even need |
|
hm, I guess you can't universally | File "/home/h/Git/trio-websocket/exc_group_cause_context/trio_websocket/_impl.py", line 226, in open_websocket
| raise copy.copy(user_error) from user_error.__cause__
| ^^^^^^^^^^^^^^^^^^^^^
| File "/usr/lib/python3.12/copy.py", line 97, in copy
| return _reconstruct(x, None, *rv)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/lib/python3.12/copy.py", line 253, in _reconstruct
| y = func(*args)
| ^^^^^^^^^^^
| File "/home/h/Git/trio-websocket/.venv/lib/python3.12/site-packages/trio/_util.py", line 374, in __call__
| raise TypeError(
| TypeError: trio.Cancelled has no public constructor |
Note that |
ah, I think in most cases it doesn't matter that we copy none of the metadata - since those will be set on the new exception object as we raise it. Writing docs/suggestion on this will be a major pain though. |
It appears it's not just | File "/home/h/Git/trio-websocket/exc_group_cause_context/trio_websocket/_impl.py", line 101, in copy_exc
| return copy.copy(e)
| ^^^^^^^^^^^^
| File "/usr/lib/python3.12/copy.py", line 97, in copy
| return _reconstruct(x, None, *rv)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/lib/python3.12/copy.py", line 253, in _reconstruct
| y = func(*args)
| ^^^^^^^^^^^
| TypeError: ConnectionRejected.__init__() missing 3 required positional arguments: 'status_code', 'headers', and 'body' I've narrowed it down to this minimal repro: import copy
class MyExc(Exception):
def __init__(self, x):
super().__init__()
a = MyExc(5)
copy.copy(a) I have no clue why Regardless, it seems like def copy_exc(e):
cls = type(e)
result = cls.__new__(cls)
result.__dict__ = copy.copy(e.__dict__)
return result which seems to handle everything I've thrown at it so far. |
That'll misbehave for any class where the |
do people do that in exception classes? And if they do, do we want to re-execute whatever code is in the I opened python/cpython#125782 to see if upstream wants to address |
In your example, you aren't passing the args to parent
If you fix that, they do get copied.
I don't think you looked at the results very carefully...
Exception objects store their state in three places:
|
Yeah that was intentional, though I perhaps went too far in minifying the repro only to cause confusion. The original code in trio-websocket is https://github.com/python-trio/trio-websocket/blob/f5fd6d77db16a7b527d670c4045fa1d53e621c35/trio_websocket/_impl.py#L587 This also displays a further thorn if trying to write a linter for it, because it doesn't directly inherit from
we don't need to copy the dunder slots for this usage, because they will all be set on the copy when we raise it. But yeah it appears my testing was overly brief. Thank you for your thorough research! ❤️ |
I realized I've done this in several PRs (at least the one in trio-websocket), and nobody has commented on the fact. It could also be a better fit for flake8-bugbear (although they don't have any type-tracking infrastructure).
I haven't yet figured out the best way of avoiding it though, options include:
also see HypothesisWorks/hypothesis#4115 for context
The text was updated successfully, but these errors were encountered: