Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
- Added special monkeypatching if `Apport <https://github.com/canonical/apport>`_ has
overridden ``sys.excepthook`` so it will format exception groups correctly
(PR by John Litborn)
- Added a backport of ``contextlib.suppress()`` from Python 3.12.1 which also handles
suppressing exceptions inside exception groups
- Fixed bare ``raise`` in a handler reraising the original naked exception rather than
an exception group which is what is raised when you do a ``raise`` in an ``except*``
handler
Expand Down
14 changes: 14 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ It contains the following:
* ``traceback.format_exception_only()``
* ``traceback.print_exception()``
* ``traceback.print_exc()``
* A backported version of ``contextlib.suppress()`` from Python 3.12.1 which also
handles suppressing exceptions inside exception groups

If this package is imported on Python 3.11 or later, the built-in implementations of the
exception group classes are used instead, ``TracebackException`` is not monkey patched
Expand Down Expand Up @@ -84,6 +86,18 @@ would be written with this backport like this:
**NOTE**: Just like with ``except*``, you cannot handle ``BaseExceptionGroup`` or
``ExceptionGroup`` with ``catch()``.

Suppressing exceptions
======================

This library contains a backport of the ``contextlib.suppress()`` context manager from
Python 3.12.1. It allows you to selectively ignore certain exceptions, even when they're
inside exception groups::

from exceptiongroup import suppress

with suppress(RuntimeError):
raise ExceptionGroup("", [RuntimeError("boo")])

Notes on monkey patching
========================

Expand Down
6 changes: 6 additions & 0 deletions src/exceptiongroup/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"format_exception_only",
"print_exception",
"print_exc",
"suppress",
]

import os
Expand Down Expand Up @@ -38,3 +39,8 @@

BaseExceptionGroup = BaseExceptionGroup
ExceptionGroup = ExceptionGroup

if sys.version_info < (3, 12, 1):
from ._suppress import suppress
else:
from contextlib import suppress
40 changes: 40 additions & 0 deletions src/exceptiongroup/_suppress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import sys
from contextlib import AbstractContextManager

if sys.version_info < (3, 11):
from ._exceptions import BaseExceptionGroup


class suppress(AbstractContextManager):
"""Backport of :class:`contextlib.suppress` from Python 3.12.1."""

def __init__(self, *exceptions):
self._exceptions = exceptions

def __enter__(self):
pass

def __exit__(self, exctype, excinst, exctb):
# Unlike isinstance and issubclass, CPython exception handling
# currently only looks at the concrete type hierarchy (ignoring
# the instance and subclass checking hooks). While Guido considers
# that a bug rather than a feature, it's a fairly hard one to fix
# due to various internal implementation details. suppress provides
# the simpler issubclass based semantics, rather than trying to
# exactly reproduce the limitations of the CPython interpreter.
#
# See http://bugs.python.org/issue12029 for more details
if exctype is None:
return

if issubclass(exctype, self._exceptions):
return True

if issubclass(exctype, BaseExceptionGroup):
match, rest = excinst.split(self._exceptions)
if rest is None:
return True

raise rest

return False
16 changes: 16 additions & 0 deletions tests/test_suppress.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import sys

import pytest

from exceptiongroup import suppress

if sys.version_info < (3, 11):
from exceptiongroup import BaseExceptionGroup, ExceptionGroup


def test_suppress_exception():
with pytest.raises(ExceptionGroup) as exc, suppress(SystemExit):
raise BaseExceptionGroup("", [SystemExit(1), RuntimeError("boo")])

assert len(exc.value.exceptions) == 1
assert isinstance(exc.value.exceptions[0], RuntimeError)