code: add ExceptionInfo.from_exception
The old-style `sys.exc_info()` triplet is redundant nowadays with `(type(exc), exc, exc.__traceback__)`, and is beginning to get soft-deprecated in Python 3.12. Add a nicer API to ExceptionInfo which takes just the exc instead of the triplet. There are already a few internal uses which benefit.
This commit is contained in:
parent
61f7c27ec0
commit
424c3eebde
|
@ -0,0 +1,2 @@
|
||||||
|
Added :func:`ExceptionInfo.from_exception() <pytest.ExceptionInfo.from_exception>`, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception.
|
||||||
|
This can replace :func:`ExceptionInfo.from_exc_info() <pytest.ExceptionInfo.from_exc_info()>` for most uses.
|
|
@ -469,22 +469,41 @@ class ExceptionInfo(Generic[E]):
|
||||||
self._traceback = traceback
|
self._traceback = traceback
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_exc_info(
|
def from_exception(
|
||||||
cls,
|
cls,
|
||||||
exc_info: Tuple[Type[E], E, TracebackType],
|
# Ignoring error: "Cannot use a covariant type variable as a parameter".
|
||||||
|
# This is OK to ignore because this class is (conceptually) readonly.
|
||||||
|
# See https://github.com/python/mypy/issues/7049.
|
||||||
|
exception: E, # type: ignore[misc]
|
||||||
exprinfo: Optional[str] = None,
|
exprinfo: Optional[str] = None,
|
||||||
) -> "ExceptionInfo[E]":
|
) -> "ExceptionInfo[E]":
|
||||||
"""Return an ExceptionInfo for an existing exc_info tuple.
|
"""Return an ExceptionInfo for an existing exception.
|
||||||
|
|
||||||
.. warning::
|
The exception must have a non-``None`` ``__traceback__`` attribute,
|
||||||
|
otherwise this function fails with an assertion error. This means that
|
||||||
Experimental API
|
the exception must have been raised, or added a traceback with the
|
||||||
|
:py:meth:`~BaseException.with_traceback()` method.
|
||||||
|
|
||||||
:param exprinfo:
|
:param exprinfo:
|
||||||
A text string helping to determine if we should strip
|
A text string helping to determine if we should strip
|
||||||
``AssertionError`` from the output. Defaults to the exception
|
``AssertionError`` from the output. Defaults to the exception
|
||||||
message/``__str__()``.
|
message/``__str__()``.
|
||||||
|
|
||||||
|
.. versionadded:: 7.4
|
||||||
"""
|
"""
|
||||||
|
assert (
|
||||||
|
exception.__traceback__
|
||||||
|
), "Exceptions passed to ExcInfo.from_exception(...) must have a non-None __traceback__."
|
||||||
|
exc_info = (type(exception), exception, exception.__traceback__)
|
||||||
|
return cls.from_exc_info(exc_info, exprinfo)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_exc_info(
|
||||||
|
cls,
|
||||||
|
exc_info: Tuple[Type[E], E, TracebackType],
|
||||||
|
exprinfo: Optional[str] = None,
|
||||||
|
) -> "ExceptionInfo[E]":
|
||||||
|
"""Like :func:`from_exception`, but using old-style exc_info tuple."""
|
||||||
_striptext = ""
|
_striptext = ""
|
||||||
if exprinfo is None and isinstance(exc_info[1], AssertionError):
|
if exprinfo is None and isinstance(exc_info[1], AssertionError):
|
||||||
exprinfo = getattr(exc_info[1], "msg", None)
|
exprinfo = getattr(exc_info[1], "msg", None)
|
||||||
|
@ -965,21 +984,13 @@ class FormattedExcinfo:
|
||||||
|
|
||||||
if e.__cause__ is not None and self.chain:
|
if e.__cause__ is not None and self.chain:
|
||||||
e = e.__cause__
|
e = e.__cause__
|
||||||
excinfo_ = (
|
excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None
|
||||||
ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
|
|
||||||
if e.__traceback__
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
descr = "The above exception was the direct cause of the following exception:"
|
descr = "The above exception was the direct cause of the following exception:"
|
||||||
elif (
|
elif (
|
||||||
e.__context__ is not None and not e.__suppress_context__ and self.chain
|
e.__context__ is not None and not e.__suppress_context__ and self.chain
|
||||||
):
|
):
|
||||||
e = e.__context__
|
e = e.__context__
|
||||||
excinfo_ = (
|
excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None
|
||||||
ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
|
|
||||||
if e.__traceback__
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
descr = "During handling of the above exception, another exception occurred:"
|
descr = "During handling of the above exception, another exception occurred:"
|
||||||
else:
|
else:
|
||||||
e = None
|
e = None
|
||||||
|
|
|
@ -950,11 +950,7 @@ def raises( # noqa: F811
|
||||||
try:
|
try:
|
||||||
func(*args[1:], **kwargs)
|
func(*args[1:], **kwargs)
|
||||||
except expected_exception as e:
|
except expected_exception as e:
|
||||||
# We just caught the exception - there is a traceback.
|
return _pytest._code.ExceptionInfo.from_exception(e)
|
||||||
assert e.__traceback__ is not None
|
|
||||||
return _pytest._code.ExceptionInfo.from_exc_info(
|
|
||||||
(type(e), e, e.__traceback__)
|
|
||||||
)
|
|
||||||
fail(message)
|
fail(message)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,20 @@ def test_excinfo_from_exc_info_simple() -> None:
|
||||||
assert info.type == ValueError
|
assert info.type == ValueError
|
||||||
|
|
||||||
|
|
||||||
|
def test_excinfo_from_exception_simple() -> None:
|
||||||
|
try:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError as e:
|
||||||
|
assert e.__traceback__ is not None
|
||||||
|
info = _pytest._code.ExceptionInfo.from_exception(e)
|
||||||
|
assert info.type == ValueError
|
||||||
|
|
||||||
|
|
||||||
|
def test_excinfo_from_exception_missing_traceback_assertion() -> None:
|
||||||
|
with pytest.raises(AssertionError, match=r"must have.*__traceback__"):
|
||||||
|
_pytest._code.ExceptionInfo.from_exception(ValueError())
|
||||||
|
|
||||||
|
|
||||||
def test_excinfo_getstatement():
|
def test_excinfo_getstatement():
|
||||||
def g():
|
def g():
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
Loading…
Reference in New Issue