From dd446bee5eb2d3ab0976309803dc77821eeac93e Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Fri, 19 Jun 2020 12:53:44 +0300 Subject: [PATCH] Fix exception causes all over the codebase --- AUTHORS | 1 + changelog/7383.bugfix.rst | 1 + src/_pytest/_code/source.py | 2 +- src/_pytest/config/__init__.py | 8 ++++---- src/_pytest/config/argparsing.py | 4 ++-- src/_pytest/config/findpaths.py | 2 +- src/_pytest/debugging.py | 6 +++--- src/_pytest/fixtures.py | 4 ++-- src/_pytest/logging.py | 4 ++-- src/_pytest/monkeypatch.py | 6 +++--- src/_pytest/python.py | 22 ++++++++++++---------- src/_pytest/warnings.py | 4 ++-- testing/test_config.py | 4 ++-- testing/test_runner.py | 4 ++-- 14 files changed, 38 insertions(+), 34 deletions(-) create mode 100644 changelog/7383.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 821a7d8f4..e40237682 100644 --- a/AUTHORS +++ b/AUTHORS @@ -233,6 +233,7 @@ Pulkit Goyal Punyashloka Biswal Quentin Pradet Ralf Schmitt +Ram Rachum Ralph Giles Ran Benita Raphael Castaneda diff --git a/changelog/7383.bugfix.rst b/changelog/7383.bugfix.rst new file mode 100644 index 000000000..d43106880 --- /dev/null +++ b/changelog/7383.bugfix.rst @@ -0,0 +1 @@ +Fixed exception causes all over the codebase, i.e. use `raise new_exception from old_exception` when wrapping an exception. diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index 3f732792f..2ccbaf657 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -215,7 +215,7 @@ class Source: newex.offset = ex.offset newex.lineno = ex.lineno newex.text = ex.text - raise newex + raise newex from ex else: if flag & ast.PyCF_ONLY_AST: assert isinstance(co, ast.AST) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 31b73a2c9..b4a5a70ad 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1189,8 +1189,8 @@ class Config: def _getini(self, name: str) -> Any: try: description, type, default = self._parser._inidict[name] - except KeyError: - raise ValueError("unknown configuration value: {!r}".format(name)) + except KeyError as e: + raise ValueError("unknown configuration value: {!r}".format(name)) from e override_value = self._get_override_ini_value(name) if override_value is None: try: @@ -1286,14 +1286,14 @@ class Config: if val is None and skip: raise AttributeError(name) return val - except AttributeError: + except AttributeError as e: 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 def getvalue(self, name, path=None): """ (deprecated, use getoption()) """ diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 985a3fd1c..084ce16e5 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -265,9 +265,9 @@ class Argument: else: try: self.dest = self._short_opts[0][1:] - except IndexError: + except IndexError as e: self.dest = "???" # Needed for the error repr. - raise ArgumentError("need a long or short option", self) + raise ArgumentError("need a long or short option", self) from e def names(self) -> List[str]: return self._short_opts + self._long_opts diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index ae8c5f47f..08a71122d 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -26,7 +26,7 @@ def _parse_ini_config(path: py.path.local) -> iniconfig.IniConfig: try: return iniconfig.IniConfig(path) except iniconfig.ParseError as exc: - raise UsageError(str(exc)) + raise UsageError(str(exc)) from exc def load_config_dict_from_file( diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 0567927c0..63126cbe0 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -28,10 +28,10 @@ def _validate_usepdb_cls(value: str) -> Tuple[str, str]: """Validate syntax of --pdbcls option.""" try: modname, classname = value.split(":") - except ValueError: + except ValueError as e: raise argparse.ArgumentTypeError( "{!r} is not in the format 'modname:classname'".format(value) - ) + ) from e return (modname, classname) @@ -130,7 +130,7 @@ class pytestPDB: value = ":".join((modname, classname)) raise UsageError( "--pdbcls: could not import {!r}: {}".format(value, exc) - ) + ) from exc else: import pdb diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 05f0ecb6a..4b2c6a774 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -938,13 +938,13 @@ def _eval_scope_callable( # Type ignored because there is no typing mechanism to specify # keyword arguments, currently. result = scope_callable(fixture_name=fixture_name, config=config) # type: ignore[call-arg] # noqa: F821 - except Exception: + except Exception as e: raise TypeError( "Error evaluating {} while defining fixture '{}'.\n" "Expected a function with the signature (*, fixture_name, config)".format( scope_callable, fixture_name ) - ) + ) from e if not isinstance(result, str): fail( "Expected {} to return a 'str' while defining fixture '{}', but it returned:\n" diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 8755e5611..a06dc1ab5 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -487,13 +487,13 @@ def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[i log_level = log_level.upper() try: return int(getattr(logging, log_level, log_level)) - except ValueError: + except ValueError as e: # Python logging does not recognise this as a logging level raise pytest.UsageError( "'{}' is not recognized as a logging level name for " "'{}'. Please consider passing the " "logging level num instead.".format(log_level, setting_name) - ) + ) from e # run after terminalreporter/capturemanager are configured diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 09f1ac36e..2e5cca526 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -73,7 +73,7 @@ def resolve(name: str) -> object: if expected == used: raise else: - raise ImportError("import error in {}: {}".format(used, ex)) + raise ImportError("import error in {}: {}".format(used, ex)) from ex found = annotated_getattr(found, part, used) return found @@ -81,12 +81,12 @@ def resolve(name: str) -> object: def annotated_getattr(obj: object, name: str, ann: str) -> object: try: obj = getattr(obj, name) - except AttributeError: + except AttributeError as e: raise AttributeError( "{!r} object at {} has no attribute {!r}".format( type(obj).__name__, ann, name ) - ) + ) from e return obj diff --git a/src/_pytest/python.py b/src/_pytest/python.py index bf45b8830..f3c42f421 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -551,8 +551,10 @@ class Module(nodes.File, PyCollector): importmode = self.config.getoption("--import-mode") try: mod = import_path(self.fspath, mode=importmode) - except SyntaxError: - raise self.CollectError(ExceptionInfo.from_current().getrepr(style="short")) + except SyntaxError as e: + raise self.CollectError( + ExceptionInfo.from_current().getrepr(style="short") + ) from e except ImportPathMismatchError as e: raise self.CollectError( "import file mismatch:\n" @@ -562,8 +564,8 @@ class Module(nodes.File, PyCollector): " %s\n" "HINT: remove __pycache__ / .pyc files and/or use a " "unique basename for your test file modules" % e.args - ) - except ImportError: + ) from e + except ImportError as e: exc_info = ExceptionInfo.from_current() if self.config.getoption("verbose") < 2: exc_info.traceback = exc_info.traceback.filter(filter_traceback) @@ -578,7 +580,7 @@ class Module(nodes.File, PyCollector): "Hint: make sure your test modules/packages have valid Python names.\n" "Traceback:\n" "{traceback}".format(fspath=self.fspath, traceback=formatted_tb) - ) + ) from e except _pytest.runner.Skipped as e: if e.allow_module_level: raise @@ -587,7 +589,7 @@ class Module(nodes.File, PyCollector): "To decorate a test function, use the @pytest.mark.skip " "or @pytest.mark.skipif decorators instead, and to skip a " "module use `pytestmark = pytest.mark.{skip,skipif}." - ) + ) from e self.config.pluginmanager.consider_module(mod) return mod @@ -836,8 +838,8 @@ class CallSpec2: def getparam(self, name: str) -> object: try: return self.params[name] - except KeyError: - raise ValueError(name) + except KeyError as e: + raise ValueError(name) from e @property def id(self) -> str: @@ -1074,8 +1076,8 @@ class Metafunc: except TypeError: try: iter(ids) - except TypeError: - raise TypeError("ids must be a callable or an iterable") + except TypeError as e: + raise TypeError("ids must be a callable or an iterable") from e num_ids = len(parameters) # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849 diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 5cedba244..f92350b20 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -48,8 +48,8 @@ def _parse_filter( lineno = int(lineno_) if lineno < 0: raise ValueError - except (ValueError, OverflowError): - raise warnings._OptionError("invalid lineno {!r}".format(lineno_)) + except (ValueError, OverflowError) as e: + raise warnings._OptionError("invalid lineno {!r}".format(lineno_)) from e else: lineno = 0 return (action, message, category, module, lineno) diff --git a/testing/test_config.py b/testing/test_config.py index c9eea7a16..4e64a6928 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1778,5 +1778,5 @@ def test_conftest_import_error_repr(tmpdir): ): try: raise RuntimeError("some error") - except Exception: - raise ConftestImportFailure(path, sys.exc_info()) + except Exception as e: + raise ConftestImportFailure(path, sys.exc_info()) from e diff --git a/testing/test_runner.py b/testing/test_runner.py index 9c19ded0e..474ff4df8 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -534,8 +534,8 @@ def test_outcomeexception_passes_except_Exception() -> None: with pytest.raises(outcomes.OutcomeException): try: raise outcomes.OutcomeException("test") - except Exception: - raise NotImplementedError() + except Exception as e: + raise NotImplementedError from e def test_pytest_exit() -> None: