From 7bc3c15fa00e4be3ee7fed15a1bdb17f1a036fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Mon, 24 Apr 2023 22:23:24 +0200 Subject: [PATCH 1/6] gh-103791: Make contextlib.suppress also act on exceptions within an ExceptionGroup --- Lib/contextlib.py | 11 ++++++++++- Lib/test/support/testcase.py | 27 +++++++++++++++++++++++++++ Lib/test/test_contextlib.py | 27 ++++++++++++++++++++++++++- Lib/test/test_except_star.py | 22 ++-------------------- 4 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 Lib/test/support/testcase.py diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 30d9ac25b2bbec..8b8958ecc0beaf 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -441,7 +441,16 @@ def __exit__(self, exctype, excinst, exctb): # exactly reproduce the limitations of the CPython interpreter. # # See http://bugs.python.org/issue12029 for more details - return exctype is not None and issubclass(exctype, self._exceptions) + if exctype is None: + return + if issubclass(exctype, self._exceptions): + return True + if issubclass(exctype, ExceptionGroup): + match, rest = excinst.split(self._exceptions) + if rest is None: + return True + raise rest from excinst + return False class _BaseExitStack: diff --git a/Lib/test/support/testcase.py b/Lib/test/support/testcase.py new file mode 100644 index 00000000000000..4fd57952c33414 --- /dev/null +++ b/Lib/test/support/testcase.py @@ -0,0 +1,27 @@ +class ExceptionIsLikeMixin: + def assertExceptionIsLike(self, exc, template): + """ + Passes when the provided `exc` matches the structure of `template`. + Individual exceptions don't have to be the same objects or even pass + an equality test: they only need to be the same type and contain equal + `exc_obj.args`.} + """ + if exc is None and template is None: + return + + if template is None: + self.fail(f"unexpected exception: {exc}") + + if exc is None: + self.fail(f"expected an exception like {template!r}, got None") + + if not isinstance(exc, ExceptionGroup): + self.assertEqual(exc.__class__, template.__class__) + self.assertEqual(exc.args[0], template.args[0]) + else: + self.assertEqual(exc.message, template.message) + print("E", exc.exceptions) + print("T", template.exceptions) + self.assertEqual(len(exc.exceptions), len(template.exceptions)) + for e, t in zip(exc.exceptions, template.exceptions): + self.assertExceptionIsLike(e, t) diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index ec06785b5667a6..0f8351ab8108a6 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -10,6 +10,7 @@ from contextlib import * # Tests __all__ from test import support from test.support import os_helper +from test.support.testcase import ExceptionIsLikeMixin import weakref @@ -1148,7 +1149,7 @@ class TestRedirectStderr(TestRedirectStream, unittest.TestCase): orig_stream = "stderr" -class TestSuppress(unittest.TestCase): +class TestSuppress(ExceptionIsLikeMixin, unittest.TestCase): @support.requires_docstrings def test_instance_docs(self): @@ -1202,6 +1203,30 @@ def test_cm_is_reentrant(self): 1/0 self.assertTrue(outer_continued) + def test_exception_groups(self): + eg_ve = lambda: ExceptionGroup( + "EG with ValueErrors only", + [ValueError("ve1"), ValueError("ve2"), ValueError("ve3")], + ) + eg_all = lambda: ExceptionGroup( + "EG with many types of exceptions", + [ValueError("ve1"), KeyError("ke1"), ValueError("ve2"), KeyError("ke2")], + ) + with suppress(ValueError): + raise eg_ve() + with suppress(ValueError, KeyError): + raise eg_all() + with self.assertRaises(ExceptionGroup) as eg1: + with suppress(ValueError): + raise eg_all() + self.assertExceptionIsLike( + eg1.exception, + ExceptionGroup( + "EG with many types of exceptions", + [KeyError("ke1"), KeyError("ke2")], + ), + ) + class TestChdir(unittest.TestCase): def make_relative_path(self, *parts): diff --git a/Lib/test/test_except_star.py b/Lib/test/test_except_star.py index c5167c5bba38af..bc66f90b9cad45 100644 --- a/Lib/test/test_except_star.py +++ b/Lib/test/test_except_star.py @@ -1,6 +1,7 @@ import sys import unittest import textwrap +from test.support.testcase import ExceptionIsLikeMixin class TestInvalidExceptStar(unittest.TestCase): def test_mixed_except_and_except_star_is_syntax_error(self): @@ -169,26 +170,7 @@ def f(x): self.assertIsInstance(exc, ExceptionGroup) -class ExceptStarTest(unittest.TestCase): - def assertExceptionIsLike(self, exc, template): - if exc is None and template is None: - return - - if template is None: - self.fail(f"unexpected exception: {exc}") - - if exc is None: - self.fail(f"expected an exception like {template!r}, got None") - - if not isinstance(exc, ExceptionGroup): - self.assertEqual(exc.__class__, template.__class__) - self.assertEqual(exc.args[0], template.args[0]) - else: - self.assertEqual(exc.message, template.message) - self.assertEqual(len(exc.exceptions), len(template.exceptions)) - for e, t in zip(exc.exceptions, template.exceptions): - self.assertExceptionIsLike(e, t) - +class ExceptStarTest(ExceptionIsLikeMixin, unittest.TestCase): def assertMetadataEqual(self, e1, e2): if e1 is None or e2 is None: self.assertTrue(e1 is None and e2 is None) From 08fa06c9a51af41fc25c1a1cf81ee2b790d0d927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Mon, 24 Apr 2023 22:39:05 +0200 Subject: [PATCH 2/6] Remove unnecessary `print()` calls --- Lib/test/support/testcase.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/test/support/testcase.py b/Lib/test/support/testcase.py index 4fd57952c33414..10999acdb4d38b 100644 --- a/Lib/test/support/testcase.py +++ b/Lib/test/support/testcase.py @@ -20,8 +20,6 @@ def assertExceptionIsLike(self, exc, template): self.assertEqual(exc.args[0], template.args[0]) else: self.assertEqual(exc.message, template.message) - print("E", exc.exceptions) - print("T", template.exceptions) self.assertEqual(len(exc.exceptions), len(template.exceptions)) for e, t in zip(exc.exceptions, template.exceptions): self.assertExceptionIsLike(e, t) From be30df6a8026239eaa76eb2737476f11da31fb3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Mon, 24 Apr 2023 22:44:36 +0200 Subject: [PATCH 3/6] Fix docstring typo --- Lib/test/support/testcase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/support/testcase.py b/Lib/test/support/testcase.py index 10999acdb4d38b..1e4363b15783eb 100644 --- a/Lib/test/support/testcase.py +++ b/Lib/test/support/testcase.py @@ -4,7 +4,7 @@ def assertExceptionIsLike(self, exc, template): Passes when the provided `exc` matches the structure of `template`. Individual exceptions don't have to be the same objects or even pass an equality test: they only need to be the same type and contain equal - `exc_obj.args`.} + `exc_obj.args`. """ if exc is None and template is None: return From acf95f6d3b25a62dd7933264d4dbeeb2fcab368f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Mon, 24 Apr 2023 23:02:11 +0200 Subject: [PATCH 4/6] Don't chain with the old exception: split already has the correct TB --- Lib/contextlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 8b8958ecc0beaf..b5acbcb9e6d77c 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -449,7 +449,7 @@ def __exit__(self, exctype, excinst, exctb): match, rest = excinst.split(self._exceptions) if rest is None: return True - raise rest from excinst + raise rest return False From 503d9f843d9506986ea87a29e17486b80358ee5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Mon, 24 Apr 2023 23:13:08 +0200 Subject: [PATCH 5/6] Documentation and Blurb --- Doc/library/contextlib.rst | 8 ++++++++ .../2023-04-24-23-07-56.gh-issue-103791.bBPWdS.rst | 3 +++ 2 files changed, 11 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-04-24-23-07-56.gh-issue-103791.bBPWdS.rst diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 1b55868c3aa62f..94ee71dfccddba 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -304,8 +304,16 @@ Functions and classes provided: This context manager is :ref:`reentrant `. + If the code within the :keyword:`!with` block raises an + :exc:`ExceptionGroup`, suppressed exceptions are removed from the + group. If any others are left, the modified group is re-raised. + Otherwise, the exception group is empty and so nothing is raised. + .. versionadded:: 3.4 + .. versionchanged:: 3.12 + ``suppress`` now supports suppressing exceptions raised as + part of an :exc:`ExceptionGroup`. .. function:: redirect_stdout(new_target) diff --git a/Misc/NEWS.d/next/Library/2023-04-24-23-07-56.gh-issue-103791.bBPWdS.rst b/Misc/NEWS.d/next/Library/2023-04-24-23-07-56.gh-issue-103791.bBPWdS.rst new file mode 100644 index 00000000000000..ffb0c8dd84886d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-24-23-07-56.gh-issue-103791.bBPWdS.rst @@ -0,0 +1,3 @@ +:class:`contextlib.suppress` now supports suppressing exceptions raised as +part of an :exc:`ExceptionGroup`. If other exceptions exist on the group, it +is now re-raised without the suppressed ones. From a43ffe46e7e6e527be9bd115bfe81d68087049ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Mon, 24 Apr 2023 23:50:35 +0200 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> --- Doc/library/contextlib.rst | 3 +-- .../Library/2023-04-24-23-07-56.gh-issue-103791.bBPWdS.rst | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 94ee71dfccddba..7cd081d1f54f43 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -306,8 +306,7 @@ Functions and classes provided: If the code within the :keyword:`!with` block raises an :exc:`ExceptionGroup`, suppressed exceptions are removed from the - group. If any others are left, the modified group is re-raised. - Otherwise, the exception group is empty and so nothing is raised. + group. If any exceptions in the group are not suppressed, a group containing them is re-raised. .. versionadded:: 3.4 diff --git a/Misc/NEWS.d/next/Library/2023-04-24-23-07-56.gh-issue-103791.bBPWdS.rst b/Misc/NEWS.d/next/Library/2023-04-24-23-07-56.gh-issue-103791.bBPWdS.rst index ffb0c8dd84886d..f00384cde9706e 100644 --- a/Misc/NEWS.d/next/Library/2023-04-24-23-07-56.gh-issue-103791.bBPWdS.rst +++ b/Misc/NEWS.d/next/Library/2023-04-24-23-07-56.gh-issue-103791.bBPWdS.rst @@ -1,3 +1,3 @@ :class:`contextlib.suppress` now supports suppressing exceptions raised as -part of an :exc:`ExceptionGroup`. If other exceptions exist on the group, it -is now re-raised without the suppressed ones. +part of an :exc:`ExceptionGroup`. If other exceptions exist on the group, they +are re-raised in a group that does not contain the suppressed exceptions. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy