New ExceptionInfo.getrepr 'chain' parameter to be able to suppress chained exceptions

This commit is contained in:
Bruno Oliveira 2018-10-12 09:37:54 -03:00
parent 933de16fe4
commit 36dc671843
2 changed files with 50 additions and 12 deletions

View File

@ -451,13 +451,35 @@ class ExceptionInfo(object):
tbfilter=True, tbfilter=True,
funcargs=False, funcargs=False,
truncate_locals=True, truncate_locals=True,
chain=True,
): ):
""" return str()able representation of this exception info. """
showlocals: show locals per traceback entry Return str()able representation of this exception info.
style: long|short|no|native traceback style
tbfilter: hide entries (where __tracebackhide__ is true)
in case of style==native, tbfilter and showlocals is ignored. :param bool showlocals:
Show locals per traceback entry.
Ignored if ``style=="native"``.
:param str style: long|short|no|native traceback style
:param bool abspath:
If paths should be changed to absolute or left unchanged.
:param bool tbfilter:
Hide entries that contain a local variable ``__tracebackhide__==True``.
Ignored if ``style=="native"``.
:param bool funcargs:
Show fixtures ("funcargs" for legacy purposes) per traceback entry.
:param bool truncate_locals:
With ``showlocals==True``, make sure locals can be safely represented as strings.
:param bool chain: if chained exceptions in Python 3 should be shown.
.. versionchanged:: 3.9
Added the ``chain`` parameter.
""" """
if style == "native": if style == "native":
return ReprExceptionInfo( return ReprExceptionInfo(
@ -476,6 +498,7 @@ class ExceptionInfo(object):
tbfilter=tbfilter, tbfilter=tbfilter,
funcargs=funcargs, funcargs=funcargs,
truncate_locals=truncate_locals, truncate_locals=truncate_locals,
chain=chain,
) )
return fmt.repr_excinfo(self) return fmt.repr_excinfo(self)
@ -516,6 +539,7 @@ class FormattedExcinfo(object):
tbfilter = attr.ib(default=True) tbfilter = attr.ib(default=True)
funcargs = attr.ib(default=False) funcargs = attr.ib(default=False)
truncate_locals = attr.ib(default=True) truncate_locals = attr.ib(default=True)
chain = attr.ib(default=True)
astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False) astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
def _getindent(self, source): def _getindent(self, source):
@ -735,7 +759,7 @@ class FormattedExcinfo(object):
reprcrash = None reprcrash = None
repr_chain += [(reprtraceback, reprcrash, descr)] repr_chain += [(reprtraceback, reprcrash, descr)]
if e.__cause__ is not None: if e.__cause__ is not None and self.chain:
e = e.__cause__ e = e.__cause__
excinfo = ( excinfo = (
ExceptionInfo((type(e), e, e.__traceback__)) ExceptionInfo((type(e), e, e.__traceback__))
@ -743,7 +767,11 @@ class FormattedExcinfo(object):
else None 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 e.__context__ is not None and not e.__suppress_context__: elif (
e.__context__ is not None
and not e.__suppress_context__
and self.chain
):
e = e.__context__ e = e.__context__
excinfo = ( excinfo = (
ExceptionInfo((type(e), e, e.__traceback__)) ExceptionInfo((type(e), e, e.__traceback__))

View File

@ -1184,20 +1184,28 @@ raise ValueError()
assert tw.lines[47] == ":15: AttributeError" assert tw.lines[47] == ":15: AttributeError"
@pytest.mark.skipif("sys.version_info[0] < 3") @pytest.mark.skipif("sys.version_info[0] < 3")
def test_exc_repr_with_raise_from_none_chain_suppression(self, importasmod): @pytest.mark.parametrize("mode", ["from_none", "explicit_suppress"])
def test_exc_repr_chain_suppression(self, importasmod, mode):
"""Check that exc repr does not show chained exceptions in Python 3.
- When the exception is raised with "from None"
- Explicitly suppressed with "chain=False" to ExceptionInfo.getrepr().
"""
raise_suffix = " from None" if mode == "from_none" else ""
mod = importasmod( mod = importasmod(
""" """
def f(): def f():
try: try:
g() g()
except Exception: except Exception:
raise AttributeError() from None raise AttributeError(){raise_suffix}
def g(): def g():
raise ValueError() raise ValueError()
""" """.format(
raise_suffix=raise_suffix
)
) )
excinfo = pytest.raises(AttributeError, mod.f) excinfo = pytest.raises(AttributeError, mod.f)
r = excinfo.getrepr(style="long") r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress")
tw = TWMock() tw = TWMock()
r.toterminal(tw) r.toterminal(tw)
for line in tw.lines: for line in tw.lines:
@ -1207,7 +1215,9 @@ raise ValueError()
assert tw.lines[2] == " try:" assert tw.lines[2] == " try:"
assert tw.lines[3] == " g()" assert tw.lines[3] == " g()"
assert tw.lines[4] == " except Exception:" assert tw.lines[4] == " except Exception:"
assert tw.lines[5] == "> raise AttributeError() from None" assert tw.lines[5] == "> raise AttributeError(){}".format(
raise_suffix
)
assert tw.lines[6] == "E AttributeError" assert tw.lines[6] == "E AttributeError"
assert tw.lines[7] == "" assert tw.lines[7] == ""
line = tw.get_write_msg(8) line = tw.get_write_msg(8)