Skip to content

Commit

Permalink
Allow creating ExceptionInfo from existing exc_info for better typing
Browse files Browse the repository at this point in the history
This way the ExceptionInfo generic parameter can be inferred from the
passed-in exc_info. See for example the replaced cast().
  • Loading branch information
bluetech committed Jul 14, 2019
1 parent 3f1fb62 commit 11f1f79
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 19 deletions.
45 changes: 32 additions & 13 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,10 +397,12 @@ class ExceptionInfo(Generic[_E]):
_traceback = attr.ib(type=Optional[Traceback], default=None)

@classmethod
def from_current(
cls, exprinfo: Optional[str] = None
) -> "ExceptionInfo[BaseException]":
"""returns an ExceptionInfo matching the current traceback
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::
Expand All @@ -411,20 +413,37 @@ def from_current(
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 and isinstance(exc_info[1], AssertionError):
exprinfo = getattr(exc_info[1], "msg", None)
if exprinfo is None:
exprinfo = saferepr(tup[1])
exprinfo = saferepr(exc_info[1])
if exprinfo and exprinfo.startswith(cls._assert_start_repr):
_striptext = "AssertionError: "

return cls(tup, _striptext)
return cls(exc_info, _striptext)

@classmethod
def from_current(
cls, exprinfo: Optional[str] = None
) -> "ExceptionInfo[BaseException]":
"""returns an ExceptionInfo matching the current traceback
.. 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__()``
"""
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]":
Expand Down
10 changes: 5 additions & 5 deletions src/_pytest/python_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
10 changes: 9 additions & 1 deletion testing/code/test_excinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,22 @@ def get_write_msg(self, idx):
fullwidth = 80


def test_excinfo_simple():
def test_excinfo_simple() -> None:
try:
raise ValueError
except ValueError:
info = _pytest._code.ExceptionInfo.from_current()
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
Expand Down

0 comments on commit 11f1f79

Please sign in to comment.