Use the proper error message with caused exceptions

When an exception is raised using "from", the chained exception goes into
__cause__, which results in a different message using Python's native traceback
handler. This patch properly replicates that error message.
This commit is contained in:
Robert Xiao 2020-11-29 16:15:11 -06:00
parent 6cff61ffbb
commit a6b337954f
3 changed files with 56 additions and 6 deletions

View file

@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed redirecting of stderr in Progress
- Fixed broken expanded tuple of one https://github.com/willmcgugan/rich/issues/445
- Fixed traceback message with `from` exceptions
## [9.2.0] - 2020-11-08

View file

@ -121,6 +121,7 @@ class Stack:
exc_type: str
exc_value: str
syntax_error: Optional[_SyntaxError] = None
is_cause: bool = False
frames: List[Frame] = field(default_factory=list)
@ -237,8 +238,13 @@ class Traceback:
"""
stacks: List[Stack] = []
is_cause = False
while True:
stack = Stack(exc_type=str(exc_type.__name__), exc_value=str(exc_value))
stack = Stack(
exc_type=str(exc_type.__name__),
exc_value=str(exc_value),
is_cause=is_cause,
)
if isinstance(exc_value, SyntaxError):
stack.syntax_error = _SyntaxError(
@ -268,12 +274,22 @@ class Traceback:
)
append(frame)
cause = getattr(exc_value, "__cause__", None)
if cause and cause.__traceback__:
exc_type = cause.__class__
exc_value = cause
traceback = cause.__traceback__
if traceback:
is_cause = True
continue
cause = exc_value.__context__
if cause and cause.__traceback__:
exc_type = cause.__class__
exc_value = cause
traceback = cause.__traceback__
if traceback:
is_cause = False
continue
# No cover, code is reached but coverage doesn't recognize it.
break # pragma: no cover
@ -347,9 +363,14 @@ class Traceback:
)
if not last:
yield Text.from_markup(
"\n[i]During handling of the above exception, another exception occurred:\n",
)
if stack.is_cause:
yield Text.from_markup(
"\n[i]The above exception was the direct cause of the following exception:\n",
)
else:
yield Text.from_markup(
"\n[i]During handling of the above exception, another exception occurred:\n",
)
@render_group()
def _render_syntax_error(self, syntax_error: _SyntaxError) -> RenderResult:

View file

@ -94,7 +94,7 @@ def test_syntax_error():
def test_nested_exception():
console = Console(width=100, file=io.StringIO())
value_error_message = "ValueError because of ZeroDivisionEerror"
value_error_message = "ValueError because of ZeroDivisionError"
try:
try:
@ -112,7 +112,35 @@ def test_nested_exception():
"During handling of the above exception",
]
assert [msg in exception_text for msg in text_should_contain]
for msg in text_should_contain:
assert msg in exception_text
# ZeroDivisionError should come before ValueError
assert exception_text.find("ZeroDivisionError") < exception_text.find("ValueError")
def test_caused_exception():
console = Console(width=100, file=io.StringIO())
value_error_message = "ValueError caused by ZeroDivisionError"
try:
try:
1 / 0
except ZeroDivisionError as e:
raise ValueError(value_error_message) from e
except Exception:
console.print_exception()
exception_text = console.file.getvalue()
text_should_contain = [
value_error_message,
"ZeroDivisionError",
"ValueError",
"The above exception was the direct cause",
]
for msg in text_should_contain:
assert msg in exception_text
# ZeroDivisionError should come before ValueError
assert exception_text.find("ZeroDivisionError") < exception_text.find("ValueError")