Allow creating ExceptionInfo from existing exc_info for better typing

This way the ExceptionInfo generic parameter can be inferred from the
passed-in exc_info. See for example the replaced cast().
This commit is contained in:
Ran Benita 2019-07-14 11:36:33 +03:00
parent 3f1fb62584
commit 11f1f79222
3 changed files with 47 additions and 20 deletions

View File

@ -396,6 +396,33 @@ class ExceptionInfo(Generic[_E]):
_striptext = attr.ib(type=str, default="")
_traceback = attr.ib(type=Optional[Traceback], default=None)
@classmethod
def from_exc_info(
cls,
exc_info: Tuple["Type[_E]", "_E", TracebackType],
exprinfo: Optional[str] = None,
) -> "ExceptionInfo[_E]":
"""returns an ExceptionInfo for an existing exc_info tuple.
.. warning::
Experimental API
:param exprinfo: a text string helping to determine if we should
strip ``AssertionError`` from the output, defaults
to the exception message/``__str__()``
"""
_striptext = ""
if exprinfo is None and isinstance(exc_info[1], AssertionError):
exprinfo = getattr(exc_info[1], "msg", None)
if exprinfo is None:
exprinfo = saferepr(exc_info[1])
if exprinfo and exprinfo.startswith(cls._assert_start_repr):
_striptext = "AssertionError: "
return cls(exc_info, _striptext)
@classmethod
def from_current(
cls, exprinfo: Optional[str] = None
@ -411,20 +438,12 @@ class ExceptionInfo(Generic[_E]):
strip ``AssertionError`` from the output, defaults
to the exception message/``__str__()``
"""
tup_ = sys.exc_info()
assert tup_[0] is not None, "no current exception"
assert tup_[1] is not None, "no current exception"
assert tup_[2] is not None, "no current exception"
tup = (tup_[0], tup_[1], tup_[2])
_striptext = ""
if exprinfo is None and isinstance(tup[1], AssertionError):
exprinfo = getattr(tup[1], "msg", None)
if exprinfo is None:
exprinfo = saferepr(tup[1])
if exprinfo and exprinfo.startswith(cls._assert_start_repr):
_striptext = "AssertionError: "
return cls(tup, _striptext)
tup = sys.exc_info()
assert tup[0] is not None, "no current exception"
assert tup[1] is not None, "no current exception"
assert tup[2] is not None, "no current exception"
exc_info = (tup[0], tup[1], tup[2])
return cls.from_exc_info(exc_info)
@classmethod
def for_later(cls) -> "ExceptionInfo[_E]":

View File

@ -707,11 +707,11 @@ def raises(
)
try:
func(*args[1:], **kwargs)
except expected_exception:
# Cast to narrow the type to expected_exception (_E).
return cast(
_pytest._code.ExceptionInfo[_E],
_pytest._code.ExceptionInfo.from_current(),
except expected_exception as e:
# We just caught the exception - there is a traceback.
assert e.__traceback__ is not None
return _pytest._code.ExceptionInfo.from_exc_info(
(type(e), e, e.__traceback__)
)
fail(message)

View File

@ -58,7 +58,7 @@ class TWMock:
fullwidth = 80
def test_excinfo_simple():
def test_excinfo_simple() -> None:
try:
raise ValueError
except ValueError:
@ -66,6 +66,14 @@ def test_excinfo_simple():
assert info.type == ValueError
def test_excinfo_from_exc_info_simple():
try:
raise ValueError
except ValueError as e:
info = _pytest._code.ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
assert info.type == ValueError
def test_excinfo_getstatement():
def g():
raise ValueError