15
15
from pathlib import Path
16
16
import re
17
17
import sys
18
- import traceback
18
+ from traceback import extract_tb
19
+ from traceback import format_exception
19
20
from traceback import format_exception_only
21
+ from traceback import FrameSummary
20
22
from types import CodeType
21
23
from types import FrameType
22
24
from types import TracebackType
28
30
from typing import Literal
29
31
from typing import overload
30
32
from typing import SupportsIndex
33
+ from typing import TYPE_CHECKING
31
34
from typing import TypeVar
32
35
from typing import Union
33
36
@@ -208,10 +211,10 @@ def with_repr_style(
208
211
def lineno (self ) -> int :
209
212
return self ._rawentry .tb_lineno - 1
210
213
211
- def get_python_framesummary (self ) -> traceback . FrameSummary :
214
+ def get_python_framesummary (self ) -> FrameSummary :
212
215
# Python's built-in traceback module implements all the nitty gritty
213
216
# details to get column numbers of out frames.
214
- stack_summary = traceback . extract_tb (self ._rawentry , limit = 1 )
217
+ stack_summary = extract_tb (self ._rawentry , limit = 1 )
215
218
return stack_summary [0 ]
216
219
217
220
# Column and end line numbers introduced in python 3.11
@@ -694,8 +697,7 @@ def getrepr(
694
697
showlocals : bool = False ,
695
698
style : TracebackStyle = "long" ,
696
699
abspath : bool = False ,
697
- tbfilter : bool
698
- | Callable [[ExceptionInfo [BaseException ]], _pytest ._code .code .Traceback ] = True ,
700
+ tbfilter : bool | Callable [[ExceptionInfo [BaseException ]], Traceback ] = True ,
699
701
funcargs : bool = False ,
700
702
truncate_locals : bool = True ,
701
703
truncate_args : bool = True ,
@@ -742,7 +744,7 @@ def getrepr(
742
744
if style == "native" :
743
745
return ReprExceptionInfo (
744
746
reprtraceback = ReprTracebackNative (
745
- traceback . format_exception (
747
+ format_exception (
746
748
self .type ,
747
749
self .value ,
748
750
self .traceback [0 ]._rawentry if self .traceback else None ,
@@ -851,6 +853,17 @@ def group_contains(
851
853
return self ._group_contains (self .value , expected_exception , match , depth )
852
854
853
855
856
+ if TYPE_CHECKING :
857
+ from typing_extensions import TypeAlias
858
+
859
+ # Type alias for the `tbfilter` setting:
860
+ # bool: If True, it should be filtered using Traceback.filter()
861
+ # callable: A callable that takes an ExceptionInfo and returns the filtered traceback.
862
+ TracebackFilter : TypeAlias = Union [
863
+ bool , Callable [[ExceptionInfo [BaseException ]], Traceback ]
864
+ ]
865
+
866
+
854
867
@dataclasses .dataclass
855
868
class FormattedExcinfo :
856
869
"""Presenting information about failing Functions and Generators."""
@@ -862,7 +875,7 @@ class FormattedExcinfo:
862
875
showlocals : bool = False
863
876
style : TracebackStyle = "long"
864
877
abspath : bool = True
865
- tbfilter : bool | Callable [[ ExceptionInfo [ BaseException ]], Traceback ] = True
878
+ tbfilter : TracebackFilter = True
866
879
funcargs : bool = False
867
880
truncate_locals : bool = True
868
881
truncate_args : bool = True
@@ -1100,11 +1113,7 @@ def _makepath(self, path: Path | str) -> str:
1100
1113
return str (path )
1101
1114
1102
1115
def repr_traceback (self , excinfo : ExceptionInfo [BaseException ]) -> ReprTraceback :
1103
- traceback = excinfo .traceback
1104
- if callable (self .tbfilter ):
1105
- traceback = self .tbfilter (excinfo )
1106
- elif self .tbfilter :
1107
- traceback = traceback .filter (excinfo )
1116
+ traceback = filter_excinfo_traceback (self .tbfilter , excinfo )
1108
1117
1109
1118
if isinstance (excinfo .value , RecursionError ):
1110
1119
traceback , extraline = self ._truncate_recursive_traceback (traceback )
@@ -1178,14 +1187,15 @@ def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainR
1178
1187
# Fall back to native traceback as a temporary workaround until
1179
1188
# full support for exception groups added to ExceptionInfo.
1180
1189
# See https://github.com/pytest-dev/pytest/issues/9159
1190
+ reprtraceback : ReprTraceback | ReprTracebackNative
1181
1191
if isinstance (e , BaseExceptionGroup ):
1182
- reprtraceback : ReprTracebackNative | ReprTraceback = (
1183
- ReprTracebackNative (
1184
- traceback . format_exception (
1185
- type ( excinfo_ . value ),
1186
- excinfo_ .value ,
1187
- excinfo_ . traceback [ 0 ]. _rawentry ,
1188
- )
1192
+ # don't filter any sub-exceptions since they shouldn't have any internal frames
1193
+ traceback = filter_excinfo_traceback ( self . tbfilter , excinfo )
1194
+ reprtraceback = ReprTracebackNative (
1195
+ format_exception (
1196
+ type ( excinfo .value ) ,
1197
+ excinfo . value ,
1198
+ traceback [ 0 ]. _rawentry ,
1189
1199
)
1190
1200
)
1191
1201
else :
@@ -1194,9 +1204,7 @@ def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainR
1194
1204
else :
1195
1205
# Fallback to native repr if the exception doesn't have a traceback:
1196
1206
# ExceptionInfo objects require a full traceback to work.
1197
- reprtraceback = ReprTracebackNative (
1198
- traceback .format_exception (type (e ), e , None )
1199
- )
1207
+ reprtraceback = ReprTracebackNative (format_exception (type (e ), e , None ))
1200
1208
reprcrash = None
1201
1209
repr_chain += [(reprtraceback , reprcrash , descr )]
1202
1210
@@ -1545,3 +1553,15 @@ def filter_traceback(entry: TracebackEntry) -> bool:
1545
1553
return False
1546
1554
1547
1555
return True
1556
+
1557
+
1558
+ def filter_excinfo_traceback (
1559
+ tbfilter : TracebackFilter , excinfo : ExceptionInfo [BaseException ]
1560
+ ) -> Traceback :
1561
+ """Filter the exception traceback in ``excinfo`` according to ``tbfilter``."""
1562
+ if callable (tbfilter ):
1563
+ return tbfilter (excinfo )
1564
+ elif tbfilter :
1565
+ return excinfo .traceback .filter (excinfo )
1566
+ else :
1567
+ return excinfo .traceback
0 commit comments