@@ -697,26 +697,79 @@ def getrepr(
697
697
)
698
698
return fmt .repr_excinfo (self )
699
699
700
+ def _stringify_exception (self , exc : BaseException ) -> str :
701
+ return "\n " .join (
702
+ [
703
+ str (exc ),
704
+ * getattr (exc , "__notes__" , []),
705
+ ]
706
+ )
707
+
700
708
def match (self , regexp : Union [str , Pattern [str ]]) -> "Literal[True]" :
701
709
"""Check whether the regular expression `regexp` matches the string
702
710
representation of the exception using :func:`python:re.search`.
703
711
704
712
If it matches `True` is returned, otherwise an `AssertionError` is raised.
705
713
"""
706
714
__tracebackhide__ = True
707
- value = "\n " .join (
708
- [
709
- str (self .value ),
710
- * getattr (self .value , "__notes__" , []),
711
- ]
712
- )
715
+ value = self ._stringify_exception (self .value )
713
716
msg = f"Regex pattern did not match.\n Regex: { regexp !r} \n Input: { value !r} "
714
717
if regexp == value :
715
718
msg += "\n Did you mean to `re.escape()` the regex?"
716
719
assert re .search (regexp , value ), msg
717
720
# Return True to allow for "assert excinfo.match()".
718
721
return True
719
722
723
+ def _group_contains (
724
+ self ,
725
+ exc_group : BaseExceptionGroup [BaseException ],
726
+ expected_exception : Union [Type [BaseException ], Tuple [Type [BaseException ], ...]],
727
+ match : Union [str , Pattern [str ], None ],
728
+ recursive : bool = False ,
729
+ ) -> bool :
730
+ """Return `True` if a `BaseExceptionGroup` contains a matching exception."""
731
+ for exc in exc_group .exceptions :
732
+ if recursive and isinstance (exc , BaseExceptionGroup ):
733
+ if self ._group_contains (exc , expected_exception , match , recursive ):
734
+ return True
735
+ if not isinstance (exc , expected_exception ):
736
+ continue
737
+ if match is not None :
738
+ value = self ._stringify_exception (exc )
739
+ if not re .search (match , value ):
740
+ continue
741
+ return True
742
+ return False
743
+
744
+ def group_contains (
745
+ self ,
746
+ expected_exception : Union [Type [BaseException ], Tuple [Type [BaseException ], ...]],
747
+ match : Union [str , Pattern [str ], None ] = None ,
748
+ recursive : bool = False ,
749
+ ) -> bool :
750
+ """Check whether a captured exception group contains a matching exception.
751
+
752
+ :param Type[BaseException] | Tuple[Type[BaseException]] expected_exception:
753
+ The expected exception type, or a tuple if one of multiple possible
754
+ exception types are expected.
755
+
756
+ :param str | Pattern[str] | None match:
757
+ If specified, a string containing a regular expression,
758
+ or a regular expression object, that is tested against the string
759
+ representation of the exception and its `PEP-678 <https://peps.python.org/pep-0678/>` `__notes__`
760
+ using :func:`re.search`.
761
+
762
+ To match a literal string that may contain :ref:`special characters
763
+ <re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
764
+
765
+ :param bool recursive:
766
+ If `True`, search will descend recursively into any nested exception groups.
767
+ If `False`, only the top exception group will be searched.
768
+ """
769
+ msg = "Captured exception is not an instance of `BaseExceptionGroup`"
770
+ assert isinstance (self .value , BaseExceptionGroup ), msg
771
+ return self ._group_contains (self .value , expected_exception , match , recursive )
772
+
720
773
721
774
@dataclasses .dataclass
722
775
class FormattedExcinfo :
0 commit comments