beautiful-traceback 0.3.0__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- beautiful_traceback/__init__.py +3 -13
- beautiful_traceback/pytest_plugin.py +88 -45
- {beautiful_traceback-0.3.0.dist-info → beautiful_traceback-0.4.0.dist-info}/METADATA +1 -1
- {beautiful_traceback-0.3.0.dist-info → beautiful_traceback-0.4.0.dist-info}/RECORD +6 -6
- {beautiful_traceback-0.3.0.dist-info → beautiful_traceback-0.4.0.dist-info}/WHEEL +0 -0
- {beautiful_traceback-0.3.0.dist-info → beautiful_traceback-0.4.0.dist-info}/entry_points.txt +0 -0
beautiful_traceback/__init__.py
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
from ._extension import load_ipython_extension # noqa: F401
|
|
2
|
-
from .formatting import LoggingFormatter, LoggingFormatterMixin
|
|
3
|
-
from .hook import install, uninstall
|
|
4
|
-
from .json_formatting import exc_to_json
|
|
2
|
+
from .formatting import LoggingFormatter, LoggingFormatterMixin # noqa: F401
|
|
3
|
+
from .hook import install, uninstall # noqa: F401
|
|
4
|
+
from .json_formatting import exc_to_json # noqa: F401
|
|
5
5
|
|
|
6
6
|
# retain typo for backward compatibility
|
|
7
7
|
LoggingFormaterMixin = LoggingFormatterMixin
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
"install",
|
|
12
|
-
"uninstall",
|
|
13
|
-
"LoggingFormatter",
|
|
14
|
-
"LoggingFormatterMixin",
|
|
15
|
-
"LoggingFormaterMixin",
|
|
16
|
-
"exc_to_json",
|
|
17
|
-
]
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any, Generator
|
|
3
|
+
|
|
1
4
|
from . import formatting
|
|
2
5
|
import pytest
|
|
3
6
|
|
|
4
7
|
from pytest import Config
|
|
5
8
|
|
|
6
9
|
|
|
7
|
-
def _get_option(config: Config, key: str):
|
|
10
|
+
def _get_option(config: Config, key: str) -> Any:
|
|
8
11
|
val = None
|
|
9
12
|
|
|
10
13
|
# will throw an exception if option is not set
|
|
@@ -56,7 +59,77 @@ def _get_exception_message_override(excinfo: pytest.ExceptionInfo) -> str | None
|
|
|
56
59
|
return message
|
|
57
60
|
|
|
58
61
|
|
|
59
|
-
def
|
|
62
|
+
def _get_pytest_assertion_details(excinfo: pytest.ExceptionInfo) -> str | None:
|
|
63
|
+
"""Return the pytest assertion diff lines for AssertionError."""
|
|
64
|
+
if not isinstance(excinfo.value, AssertionError):
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
# pytest stores assertion diffs on its own repr object, not the exception.
|
|
69
|
+
# Reference: https://github.com/pytest-dev/pytest/blob/main/src/_pytest/_code/code.py
|
|
70
|
+
repr_info = excinfo.getrepr(style="long")
|
|
71
|
+
except Exception:
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
reprtraceback = getattr(repr_info, "reprtraceback", None)
|
|
75
|
+
if reprtraceback is None:
|
|
76
|
+
chain = getattr(repr_info, "chain", None)
|
|
77
|
+
if chain:
|
|
78
|
+
reprtraceback = chain[-1][0]
|
|
79
|
+
|
|
80
|
+
if reprtraceback is None:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
reprentries = getattr(reprtraceback, "reprentries", None)
|
|
84
|
+
if not reprentries:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
last_entry = reprentries[-1]
|
|
88
|
+
entry_lines = getattr(last_entry, "lines", None)
|
|
89
|
+
if not entry_lines:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
# Keep only the assertion diff lines for concise appending.
|
|
93
|
+
lines = []
|
|
94
|
+
for line in entry_lines:
|
|
95
|
+
# Keep pytest's assertion diff lines and the failing expression.
|
|
96
|
+
stripped = line.lstrip()
|
|
97
|
+
if stripped.startswith("E"):
|
|
98
|
+
lines.append(stripped)
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
# Include the source line marker when present.
|
|
102
|
+
if stripped.startswith(">"):
|
|
103
|
+
lines.append(stripped)
|
|
104
|
+
|
|
105
|
+
if not lines:
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
return os.linesep.join(lines)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _format_traceback(excinfo: pytest.ExceptionInfo, config: Config) -> str:
|
|
112
|
+
"""Format a traceback with beautiful_traceback styling and pytest assertion details."""
|
|
113
|
+
message_override = _get_exception_message_override(excinfo)
|
|
114
|
+
assertion_details = _get_pytest_assertion_details(excinfo)
|
|
115
|
+
|
|
116
|
+
formatted_traceback = formatting.exc_to_traceback_str(
|
|
117
|
+
excinfo.value,
|
|
118
|
+
excinfo.tb,
|
|
119
|
+
color=True,
|
|
120
|
+
local_stack_only=_get_option(
|
|
121
|
+
config, "enable_beautiful_traceback_local_stack_only"
|
|
122
|
+
),
|
|
123
|
+
exc_msg_override=message_override,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if assertion_details:
|
|
127
|
+
formatted_traceback += os.linesep + assertion_details + os.linesep
|
|
128
|
+
|
|
129
|
+
return formatted_traceback
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def pytest_addoption(parser) -> None:
|
|
60
133
|
parser.addini(
|
|
61
134
|
"enable_beautiful_traceback",
|
|
62
135
|
"Enable the beautiful traceback plugin",
|
|
@@ -73,54 +146,24 @@ def pytest_addoption(parser):
|
|
|
73
146
|
|
|
74
147
|
|
|
75
148
|
@pytest.hookimpl(hookwrapper=True)
|
|
76
|
-
def pytest_runtest_makereport(item, call):
|
|
77
|
-
"""
|
|
78
|
-
Pytest stack traces are challenging to work with by default. This plugin allows beautiful_traceback to be used instead.
|
|
149
|
+
def pytest_runtest_makereport(item, call) -> Generator[None, None, None]:
|
|
150
|
+
"""Format test execution tracebacks with beautiful_traceback.
|
|
79
151
|
|
|
80
|
-
This
|
|
81
|
-
|
|
82
|
-
https://grok.com/share/bGVnYWN5_951be3b1-6811-4fda-b220-c1dd72dedc31
|
|
152
|
+
This hook runs during the test execution phase and replaces pytest's
|
|
153
|
+
default traceback formatting with beautiful_traceback's output.
|
|
83
154
|
"""
|
|
84
|
-
outcome = yield
|
|
85
|
-
report = outcome.get_result() #
|
|
155
|
+
outcome = yield # type: ignore[misc]
|
|
156
|
+
report = outcome.get_result() # type: ignore[attr-defined]
|
|
86
157
|
|
|
87
|
-
# Check if the report is for the 'call' phase (test execution) and if it failed
|
|
88
158
|
if _get_option(item.config, "enable_beautiful_traceback") and report.failed:
|
|
89
|
-
|
|
90
|
-
tb = call.excinfo.tb
|
|
91
|
-
|
|
92
|
-
message_override = _get_exception_message_override(call.excinfo)
|
|
159
|
+
report.longrepr = _format_traceback(call.excinfo, item.config)
|
|
93
160
|
|
|
94
|
-
formatted_traceback = formatting.exc_to_traceback_str(
|
|
95
|
-
value,
|
|
96
|
-
tb,
|
|
97
|
-
color=True,
|
|
98
|
-
local_stack_only=_get_option(
|
|
99
|
-
item.config, "enable_beautiful_traceback_local_stack_only"
|
|
100
|
-
),
|
|
101
|
-
exc_msg_override=message_override,
|
|
102
|
-
)
|
|
103
|
-
report.longrepr = formatted_traceback
|
|
104
161
|
|
|
162
|
+
def pytest_exception_interact(node, call, report) -> None:
|
|
163
|
+
"""Format collection-phase tracebacks with beautiful_traceback.
|
|
105
164
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
This can run during collection, not just test execution.
|
|
109
|
-
|
|
110
|
-
So, if there's an import or other pre-run error in pytest, this will apply the correct formatting.
|
|
165
|
+
This hook runs during collection (e.g., import errors, fixture errors)
|
|
166
|
+
and ensures those errors also use beautiful_traceback formatting.
|
|
111
167
|
"""
|
|
112
|
-
if report.failed:
|
|
113
|
-
|
|
114
|
-
tb = call.excinfo.tb
|
|
115
|
-
message_override = _get_exception_message_override(call.excinfo)
|
|
116
|
-
|
|
117
|
-
formatted_traceback = formatting.exc_to_traceback_str(
|
|
118
|
-
value,
|
|
119
|
-
tb,
|
|
120
|
-
color=True,
|
|
121
|
-
local_stack_only=_get_option(
|
|
122
|
-
node.config, "enable_beautiful_traceback_local_stack_only"
|
|
123
|
-
),
|
|
124
|
-
exc_msg_override=message_override,
|
|
125
|
-
)
|
|
126
|
-
report.longrepr = formatted_traceback
|
|
168
|
+
if _get_option(node.config, "enable_beautiful_traceback") and report.failed:
|
|
169
|
+
report.longrepr = _format_traceback(call.excinfo, node.config)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
beautiful_traceback/__init__.py,sha256=
|
|
1
|
+
beautiful_traceback/__init__.py,sha256=ZDE4AcfeeT420hCntDfdhnp4uhXm1YwCQHsTuvGc0L4,332
|
|
2
2
|
beautiful_traceback/_extension.py,sha256=klyo3XL4q3-Wdy4Lt6JYdh-Cfh_SkRCI7jCcQCwfWTM,311
|
|
3
3
|
beautiful_traceback/cli.py,sha256=M4EWW9SUNiGH2VCpssxRDMNXNjrVpy_8t9T7jf66RKE,2405
|
|
4
4
|
beautiful_traceback/common.py,sha256=Dg6J4rLdX9uKM6LxJaqSwtLkUjggZk-KrR5kpAIA4uo,2208
|
|
@@ -6,8 +6,8 @@ beautiful_traceback/formatting.py,sha256=s4tjdejhYBXBmHlHiUa3bm9j0I6B2hka46JfHNd
|
|
|
6
6
|
beautiful_traceback/hook.py,sha256=6vYpqA-mD4G32HkX5WSyCMzkvMwB4RXP6wbVEIU6oaY,2078
|
|
7
7
|
beautiful_traceback/json_formatting.py,sha256=WcgA6rk6YkFDRLXOTk95yVrK_HxNjvHHUqgr2RlK_O4,6071
|
|
8
8
|
beautiful_traceback/parsing.py,sha256=39GvHo6kx5dyzTMfRjUTc8H9tRGIhFT8RcjxUvbZBZY,3094
|
|
9
|
-
beautiful_traceback/pytest_plugin.py,sha256=
|
|
10
|
-
beautiful_traceback-0.
|
|
11
|
-
beautiful_traceback-0.
|
|
12
|
-
beautiful_traceback-0.
|
|
13
|
-
beautiful_traceback-0.
|
|
9
|
+
beautiful_traceback/pytest_plugin.py,sha256=6UtsNuFMMc2Ga5Uvl6ctNGoPNdzGKg9NFAwrz5LzPGk,5132
|
|
10
|
+
beautiful_traceback-0.4.0.dist-info/WHEEL,sha256=XV0cjMrO7zXhVAIyyc8aFf1VjZ33Fen4IiJk5zFlC3g,80
|
|
11
|
+
beautiful_traceback-0.4.0.dist-info/entry_points.txt,sha256=EXsu7N89wqDpZPEwLHgYVncZJ3y-lbCFZC5fKBZZDac,138
|
|
12
|
+
beautiful_traceback-0.4.0.dist-info/METADATA,sha256=qVYJrlUdgW6lSpelAoujC8OHOy2pJ43COqEiGjXkiww,8558
|
|
13
|
+
beautiful_traceback-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
{beautiful_traceback-0.3.0.dist-info → beautiful_traceback-0.4.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|