-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Fix exception causes in config/__init__.py #7351
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
Conversation
I'm 👍 on the idea, thanks for the offer! Are there still more places in the codebase where this adjustment is needed, or this PR covers them all? |
Awesome! No, there are a few more:
Some of these will be false-positives, I'll go over all of them and give you a separate PR after we're done with this PR. |
Thanks! Wouldn't be better to just change them in this PR instead though? |
I could do that, but before I spend time going over these cases, I want to be sure there wouldn't be any roadblock. Getting this PR merged would be a good indication on whether there'll be a roadblock. |
Well the tests passing is a good indicator that things will probably go smooth. 😉 But let's wait for other maintainers to chime in before you spend more time on this. 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely +1 from me on proper exception chaining.
I've pointed several places where I think from None
is more appropriate.
src/_pytest/config/__init__.py
Outdated
except KeyError as e: | ||
raise ValueError("unknown configuration value: {!r}".format(name)) from e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I think from None
would be better.
except KeyError as e: | |
raise ValueError("unknown configuration value: {!r}".format(name)) from e | |
except KeyError: | |
raise ValueError("unknown configuration value: {!r}".format(name)) from None |
src/_pytest/config/__init__.py
Outdated
@@ -1223,14 +1223,14 @@ def getoption(self, name: str, default=notset, skip: bool = False): | |||
if val is None and skip: | |||
raise AttributeError(name) | |||
return val | |||
except AttributeError: | |||
except AttributeError as e: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
except AttributeError as e: | |
except AttributeError: |
src/_pytest/config/__init__.py
Outdated
if default is not notset: | ||
return default | ||
if skip: | ||
import pytest | ||
|
||
pytest.skip("no {!r} option found".format(name)) | ||
raise ValueError("no option named {!r}".format(name)) | ||
raise ValueError("no option named {!r}".format(name)) from e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raise ValueError("no option named {!r}".format(name)) from e | |
raise ValueError("no option named {!r}".format(name)) from None |
@@ -641,7 +641,7 @@ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> No | |||
except ImportError as e: | |||
raise ImportError( | |||
'Error importing plugin "{}": {}'.format(modname, str(e.args[0])) | |||
).with_traceback(e.__traceback__) | |||
).with_traceback(e.__traceback__) from e |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The with_traceback
makes me think maybe the intention here is from None
.
My position is that I prefer If you agree, I'll just remove my changes for all the cases where you've done |
@cool-RR Of course I agree :) I'd be interested to understand your position though. For example in this case try:
description, type, default = self._parser._inidict[name]
except KeyError as e:
raise ValueError("unknown configuration value: {!r}".format(name)) from e The if name in self._parser._inidict:
description, type, default = self._parser._inidict[name]
else:
raise ValueError("unknown configuration value: {!r}".format(name)) |
I'll happily explain my position :) I personally hate In this case, I would have wanted to see the Another issue is that some error-reporting systems show not only the traceback, but all the local variables on each of these levels. These supposedly irrelevant levels could have variables that make it clearer what this exception is about. |
Thanks for explaining your position! So in some cases, like debugging crashes or unexpected exceptions, I fully agree. But in the cases in the PR, the intention is to report some error to the user, and here I subscribe to a different approach, which is to make the errors as concise and to-the-point as possible. But, I shall not force you to go against your principles 😄 Since a direct cause is already an improvement over an indirect cause (which to me in Python 3 means a programmer error), it's fine by me. |
129dbed
to
caa984c
Compare
Cool :) I changed the cases you mentioned, except the one on line 644. I think that the |
Thanks @cool-RR! |
I just made a blog post about this change here: https://blog.ram.rachum.com/post/621791438475296768/improving-python-exception-chaining-with |
I recently went over Matplotlib, Pandas and NumPy, fixing a small mistake in the way that Python 3's exception chaining is used. If you're interested, I can do it here too. I've done it on just one file right now.
The mistake is this: In some parts of the code, an exception is being caught and replaced with a more user-friendly error. In these cases the syntax
raise new_error from old_error
needs to be used.Python 3's exception chaining means it shows not only the traceback of the current exception, but that of the original exception (and possibly more.) This is regardless of
raise from
. The usage ofraise from
tells Python to put a more accurate message between the tracebacks. Instead of this:You'll get this:
The first is inaccurate, because it signifies a bug in the exception-handling code itself, which is a separate situation than wrapping an exception.
Let me know what you think!