Skip to content

Commit

Permalink
Backported contextlib.suppress from Python 3.12.1 (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
agronholm committed Nov 21, 2023
1 parent 1ede26f commit f7c00cf
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 0 deletions.
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)

0 comments on commit f7c00cf

Please sign in to comment.