Skip to content

Commit 3e02d6b

Browse files
RonnyPfannschmidtClaude Fable 5
andcommitted
Modernize ConftestImportFailure to use the exception chain
Give ConftestImportFailure the same shape PluginImportFailure just got: a bare Exception subclass whose argument is the conftest path, with the original error carried by `raise ... from` on __cause__ instead of a hand-rolled `cause` attribute duplicating it. The custom __init__ and __str__ go away; str(e) is the path, which is exactly what the two message consumers want. The `cause` attribute dates to 8.0 (e1074f9), which replaced the old excinfo triplet without deprecation; the class is private to _pytest.config and a code search finds no external consumers, only vendored copies of pytest itself. No user-visible output changes. Co-Authored-By: Claude Fable 5 <ai@anthropic.com> Co-Authored-By: Claude Code <ai@anthropic.com>
1 parent 98fcf26 commit 3e02d6b

5 files changed

Lines changed: 18 additions & 24 deletions

File tree

changelog/14943.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
``ConftestImportFailure`` no longer carries a ``cause`` attribute; the original error is available as ``__cause__`` via the regular exception chain.

src/_pytest/config/__init__.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,11 @@ class ExitCode(enum.IntEnum):
129129

130130

131131
class ConftestImportFailure(Exception):
132-
def __init__(
133-
self,
134-
path: pathlib.Path,
135-
*,
136-
cause: Exception,
137-
) -> None:
138-
self.path = path
139-
self.cause = cause
132+
"""A conftest.py raised while being imported.
140133
141-
def __str__(self) -> str:
142-
return f"{type(self.cause).__name__}: {self.cause} (from {self.path})"
134+
The path of the failing conftest is the exception argument; the original
135+
error is chained as ``__cause__``.
136+
"""
143137

144138

145139
class PluginImportFailure(Exception):
@@ -179,9 +173,8 @@ def _print_import_error(header: str, cause: BaseException, file: TextIO) -> None
179173

180174

181175
def print_conftest_import_error(e: ConftestImportFailure, file: TextIO) -> None:
182-
_print_import_error(
183-
f"ImportError while loading conftest '{e.path}'.", e.cause, file
184-
)
176+
assert e.__cause__ is not None, f"{e!r} must be raised `from` the original error"
177+
_print_import_error(f"ImportError while loading conftest '{e}'.", e.__cause__, file)
185178

186179

187180
def print_plugin_import_error(e: PluginImportFailure, file: TextIO) -> None:
@@ -793,7 +786,7 @@ def _importconftest(
793786
)
794787
except Exception as e:
795788
assert e.__traceback__ is not None
796-
raise ConftestImportFailure(conftestpath, cause=e) from e
789+
raise ConftestImportFailure(conftestpath) from e
797790

798791
self._check_non_top_pytest_plugins(mod, conftestpath)
799792

@@ -1748,7 +1741,7 @@ def parse(self, args: list[str], addopts: bool = True) -> None:
17481741
# we don't want to prevent --help/--version to work
17491742
# so just let it pass and print a warning at the end
17501743
self.issue_config_time_warning(
1751-
PytestConfigWarning(f"could not load initial conftests: {e.path}"),
1744+
PytestConfigWarning(f"could not load initial conftests: {e}"),
17521745
stacklevel=2,
17531746
)
17541747
else:

src/_pytest/debugging.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,8 @@ def _postmortem_exc_or_tb(
382382
elif isinstance(excinfo.value, ConftestImportFailure):
383383
# A config.ConftestImportFailure is not useful for post_mortem.
384384
# Use the underlying exception instead:
385-
cause = excinfo.value.cause
385+
cause = excinfo.value.__cause__
386+
assert cause is not None
386387
if get_exc:
387388
return cause
388389

src/_pytest/nodes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,8 @@ def _repr_failure_py(
412412
from _pytest.fixtures import FixtureLookupError
413413

414414
if isinstance(excinfo.value, ConftestImportFailure):
415-
excinfo = ExceptionInfo.from_exception(excinfo.value.cause)
415+
assert excinfo.value.__cause__ is not None
416+
excinfo = ExceptionInfo.from_exception(excinfo.value.__cause__)
416417
if isinstance(excinfo.value, fail.Exception):
417418
if not excinfo.value.pytrace:
418419
style = "value"

testing/test_config.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3038,17 +3038,15 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives
30383038

30393039

30403040
def test_conftest_import_error_repr(tmp_path: Path) -> None:
3041-
"""`ConftestImportFailure` should use a short error message and readable
3042-
path to the failed conftest.py file."""
3041+
"""`ConftestImportFailure` carries the failed conftest.py path as its
3042+
argument and the original error as its cause."""
30433043
path = tmp_path.joinpath("foo/conftest.py")
3044-
with pytest.raises(
3045-
ConftestImportFailure,
3046-
match=re.escape(f"RuntimeError: some error (from {path})"),
3047-
):
3044+
with pytest.raises(ConftestImportFailure, match=re.escape(str(path))) as excinfo:
30483045
try:
30493046
raise RuntimeError("some error")
30503047
except Exception as exc:
3051-
raise ConftestImportFailure(path, cause=exc) from exc
3048+
raise ConftestImportFailure(path) from exc
3049+
assert str(excinfo.value.__cause__) == "some error"
30523050

30533051

30543052
def test_strtobool() -> None:

0 commit comments

Comments
 (0)