Skip to content

Commit 5d13853

Browse files
authored
Merge pull request #10901 from bluetech/exceptioninfo-from-exception
code: add `ExceptionInfo.from_exception`
2 parents eff54ae + bc1fc3f commit 5d13853

File tree

4 files changed

+45
-18
lines changed

4 files changed

+45
-18
lines changed

changelog/10901.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Added :func:`ExceptionInfo.from_exception() <pytest.ExceptionInfo.from_exception>`, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception.
2+
This can replace :func:`ExceptionInfo.from_exc_info() <pytest.ExceptionInfo.from_exc_info()>` for most uses.

src/_pytest/_code/code.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -469,18 +469,41 @@ def __init__(
469469
self._traceback = traceback
470470

471471
@classmethod
472-
def from_exc_info(
472+
def from_exception(
473473
cls,
474-
exc_info: Tuple[Type[E], E, TracebackType],
474+
# Ignoring error: "Cannot use a covariant type variable as a parameter".
475+
# This is OK to ignore because this class is (conceptually) readonly.
476+
# See https://github.com/python/mypy/issues/7049.
477+
exception: E, # type: ignore[misc]
475478
exprinfo: Optional[str] = None,
476479
) -> "ExceptionInfo[E]":
477-
"""Return an ExceptionInfo for an existing exc_info tuple.
480+
"""Return an ExceptionInfo for an existing exception.
481+
482+
The exception must have a non-``None`` ``__traceback__`` attribute,
483+
otherwise this function fails with an assertion error. This means that
484+
the exception must have been raised, or added a traceback with the
485+
:py:meth:`~BaseException.with_traceback()` method.
478486
479487
:param exprinfo:
480488
A text string helping to determine if we should strip
481489
``AssertionError`` from the output. Defaults to the exception
482490
message/``__str__()``.
491+
492+
.. versionadded:: 7.4
483493
"""
494+
assert (
495+
exception.__traceback__
496+
), "Exceptions passed to ExcInfo.from_exception(...) must have a non-None __traceback__."
497+
exc_info = (type(exception), exception, exception.__traceback__)
498+
return cls.from_exc_info(exc_info, exprinfo)
499+
500+
@classmethod
501+
def from_exc_info(
502+
cls,
503+
exc_info: Tuple[Type[E], E, TracebackType],
504+
exprinfo: Optional[str] = None,
505+
) -> "ExceptionInfo[E]":
506+
"""Like :func:`from_exception`, but using old-style exc_info tuple."""
484507
_striptext = ""
485508
if exprinfo is None and isinstance(exc_info[1], AssertionError):
486509
exprinfo = getattr(exc_info[1], "msg", None)
@@ -954,21 +977,13 @@ def repr_excinfo(
954977
repr_chain += [(reprtraceback, reprcrash, descr)]
955978
if e.__cause__ is not None and self.chain:
956979
e = e.__cause__
957-
excinfo_ = (
958-
ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
959-
if e.__traceback__
960-
else None
961-
)
980+
excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None
962981
descr = "The above exception was the direct cause of the following exception:"
963982
elif (
964983
e.__context__ is not None and not e.__suppress_context__ and self.chain
965984
):
966985
e = e.__context__
967-
excinfo_ = (
968-
ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
969-
if e.__traceback__
970-
else None
971-
)
986+
excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None
972987
descr = "During handling of the above exception, another exception occurred:"
973988
else:
974989
e = None

src/_pytest/python_api.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -950,11 +950,7 @@ def raises( # noqa: F811
950950
try:
951951
func(*args[1:], **kwargs)
952952
except expected_exception as e:
953-
# We just caught the exception - there is a traceback.
954-
assert e.__traceback__ is not None
955-
return _pytest._code.ExceptionInfo.from_exc_info(
956-
(type(e), e, e.__traceback__)
957-
)
953+
return _pytest._code.ExceptionInfo.from_exception(e)
958954
fail(message)
959955

960956

testing/code/test_excinfo.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ def test_excinfo_from_exc_info_simple() -> None:
5353
assert info.type == ValueError
5454

5555

56+
def test_excinfo_from_exception_simple() -> None:
57+
try:
58+
raise ValueError
59+
except ValueError as e:
60+
assert e.__traceback__ is not None
61+
info = _pytest._code.ExceptionInfo.from_exception(e)
62+
assert info.type == ValueError
63+
64+
65+
def test_excinfo_from_exception_missing_traceback_assertion() -> None:
66+
with pytest.raises(AssertionError, match=r"must have.*__traceback__"):
67+
_pytest._code.ExceptionInfo.from_exception(ValueError())
68+
69+
5670
def test_excinfo_getstatement():
5771
def g():
5872
raise ValueError

0 commit comments

Comments
 (0)