1515from pathlib import Path
1616import re
1717import sys
18- import traceback
18+ from traceback import extract_tb
19+ from traceback import format_exception
1920from traceback import format_exception_only
21+ from traceback import FrameSummary
2022from types import CodeType
2123from types import FrameType
2224from types import TracebackType
2830from typing import Literal
2931from typing import overload
3032from typing import SupportsIndex
33+ from typing import TYPE_CHECKING
3134from typing import TypeVar
3235from typing import Union
3336
@@ -208,10 +211,10 @@ def with_repr_style(
208211 def lineno (self ) -> int :
209212 return self ._rawentry .tb_lineno - 1
210213
211- def get_python_framesummary (self ) -> traceback . FrameSummary :
214+ def get_python_framesummary (self ) -> FrameSummary :
212215 # Python's built-in traceback module implements all the nitty gritty
213216 # 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 )
215218 return stack_summary [0 ]
216219
217220 # Column and end line numbers introduced in python 3.11
@@ -694,8 +697,7 @@ def getrepr(
694697 showlocals : bool = False ,
695698 style : TracebackStyle = "long" ,
696699 abspath : bool = False ,
697- tbfilter : bool
698- | Callable [[ExceptionInfo [BaseException ]], _pytest ._code .code .Traceback ] = True ,
700+ tbfilter : bool | Callable [[ExceptionInfo [BaseException ]], Traceback ] = True ,
699701 funcargs : bool = False ,
700702 truncate_locals : bool = True ,
701703 truncate_args : bool = True ,
@@ -742,7 +744,7 @@ def getrepr(
742744 if style == "native" :
743745 return ReprExceptionInfo (
744746 reprtraceback = ReprTracebackNative (
745- traceback . format_exception (
747+ format_exception (
746748 self .type ,
747749 self .value ,
748750 self .traceback [0 ]._rawentry if self .traceback else None ,
@@ -851,6 +853,17 @@ def group_contains(
851853 return self ._group_contains (self .value , expected_exception , match , depth )
852854
853855
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+
854867@dataclasses .dataclass
855868class FormattedExcinfo :
856869 """Presenting information about failing Functions and Generators."""
@@ -862,7 +875,7 @@ class FormattedExcinfo:
862875 showlocals : bool = False
863876 style : TracebackStyle = "long"
864877 abspath : bool = True
865- tbfilter : bool | Callable [[ ExceptionInfo [ BaseException ]], Traceback ] = True
878+ tbfilter : TracebackFilter = True
866879 funcargs : bool = False
867880 truncate_locals : bool = True
868881 truncate_args : bool = True
@@ -1100,11 +1113,7 @@ def _makepath(self, path: Path | str) -> str:
11001113 return str (path )
11011114
11021115 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 )
11081117
11091118 if isinstance (excinfo .value , RecursionError ):
11101119 traceback , extraline = self ._truncate_recursive_traceback (traceback )
@@ -1178,14 +1187,15 @@ def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainR
11781187 # Fall back to native traceback as a temporary workaround until
11791188 # full support for exception groups added to ExceptionInfo.
11801189 # See https://github.com/pytest-dev/pytest/issues/9159
1190+ reprtraceback : ReprTraceback | ReprTracebackNative
11811191 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 ,
11891199 )
11901200 )
11911201 else :
@@ -1194,9 +1204,7 @@ def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainR
11941204 else :
11951205 # Fallback to native repr if the exception doesn't have a traceback:
11961206 # 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 ))
12001208 reprcrash = None
12011209 repr_chain += [(reprtraceback , reprcrash , descr )]
12021210
@@ -1545,3 +1553,15 @@ def filter_traceback(entry: TracebackEntry) -> bool:
15451553 return False
15461554
15471555 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