diff --git a/changelog/14943.misc.rst b/changelog/14943.misc.rst new file mode 100644 index 00000000000..365d5fa6432 --- /dev/null +++ b/changelog/14943.misc.rst @@ -0,0 +1 @@ +``ConftestImportFailure`` no longer carries a ``cause`` attribute; the original error is available as ``__cause__`` via the regular exception chain. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 60ba5f54a89..3a3a743474e 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -132,17 +132,11 @@ class ExitCode(enum.IntEnum): class ConftestImportFailure(Exception): - def __init__( - self, - path: pathlib.Path, - *, - cause: Exception, - ) -> None: - self.path = path - self.cause = cause + """A conftest.py raised while being imported. - def __str__(self) -> str: - return f"{type(self.cause).__name__}: {self.cause} (from {self.path})" + The path of the failing conftest is the exception argument; the original + error is chained as ``__cause__``. + """ class PluginImportFailure(Exception): @@ -182,9 +176,8 @@ def _print_import_error(header: str, cause: BaseException, file: TextIO) -> None def print_conftest_import_error(e: ConftestImportFailure, file: TextIO) -> None: - _print_import_error( - f"ImportError while loading conftest '{e.path}'.", e.cause, file - ) + assert e.__cause__ is not None, f"{e!r} must be raised `from` the original error" + _print_import_error(f"ImportError while loading conftest '{e}'.", e.__cause__, file) def print_plugin_import_error(e: PluginImportFailure, file: TextIO) -> None: @@ -793,7 +786,7 @@ def _importconftest( ) except Exception as e: assert e.__traceback__ is not None - raise ConftestImportFailure(conftestpath, cause=e) from e + raise ConftestImportFailure(conftestpath) from e self._check_non_top_pytest_plugins(mod, conftestpath) @@ -1796,7 +1789,7 @@ def parse(self, args: list[str], addopts: bool = True) -> None: # we don't want to prevent --help/--version to work # so just let it pass and print a warning at the end self.issue_config_time_warning( - PytestConfigWarning(f"could not load initial conftests: {e.path}"), + PytestConfigWarning(f"could not load initial conftests: {e}"), stacklevel=2, ) else: diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index b256f83c8bf..09121876d31 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -382,7 +382,8 @@ def _postmortem_exc_or_tb( elif isinstance(excinfo.value, ConftestImportFailure): # A config.ConftestImportFailure is not useful for post_mortem. # Use the underlying exception instead: - cause = excinfo.value.cause + cause = excinfo.value.__cause__ + assert cause is not None if get_exc: return cause diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 2b510907122..47af905d673 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -434,7 +434,8 @@ def _repr_failure_py( from _pytest.fixtures import FixtureLookupError if isinstance(excinfo.value, ConftestImportFailure): - excinfo = ExceptionInfo.from_exception(excinfo.value.cause) + assert excinfo.value.__cause__ is not None + excinfo = ExceptionInfo.from_exception(excinfo.value.__cause__) if isinstance(excinfo.value, fail.Exception): if not excinfo.value.pytrace: style = "value" diff --git a/testing/test_config.py b/testing/test_config.py index 282e66409f7..6cf2ad3b848 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -3122,17 +3122,15 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives def test_conftest_import_error_repr(tmp_path: Path) -> None: - """`ConftestImportFailure` should use a short error message and readable - path to the failed conftest.py file.""" + """`ConftestImportFailure` carries the failed conftest.py path as its + argument and the original error as its cause.""" path = tmp_path.joinpath("foo/conftest.py") - with pytest.raises( - ConftestImportFailure, - match=re.escape(f"RuntimeError: some error (from {path})"), - ): + with pytest.raises(ConftestImportFailure, match=re.escape(str(path))) as excinfo: try: raise RuntimeError("some error") except Exception as exc: - raise ConftestImportFailure(path, cause=exc) from exc + raise ConftestImportFailure(path) from exc + assert str(excinfo.value.__cause__) == "some error" def test_strtobool() -> None: