From 95b78198e16bc3620c35fdc06ccd3c2f9d4b2a79 Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Sun, 12 Mar 2023 14:17:29 -0700 Subject: [PATCH] Work around CPython bug in `HTTPError` (#58) --- CHANGES.rst | 6 ++++++ src/exceptiongroup/_formatting.py | 11 ++++++++++- tests/test_formatting.py | 8 ++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 52b6897..47a56a4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,12 @@ Version history This library adheres to `Semantic Versioning 2.0 `_. +**UNRELEASED** + +- Workaround for [CPython issue #98778](https://github.com/python/cpython/issues/98778), + ``urllib.error.HTTPError(..., fp=None)`` raises ``KeyError`` on unknown attribute access, + on affected Python versions. (PR by Zac Hatfield-Dodds) + **1.1.0** - Backported upstream fix for gh-99553 (custom subclasses of ``BaseExceptionGroup`` that diff --git a/src/exceptiongroup/_formatting.py b/src/exceptiongroup/_formatting.py index 4ff269b..c0402ba 100644 --- a/src/exceptiongroup/_formatting.py +++ b/src/exceptiongroup/_formatting.py @@ -103,7 +103,16 @@ def __init__( # Capture now to permit freeing resources: only complication is in the # unofficial API _format_final_exc_line self._str = _safe_string(exc_value, "exception") - self.__notes__ = getattr(exc_value, "__notes__", None) + try: + self.__notes__ = getattr(exc_value, "__notes__", None) + except KeyError: + # Workaround for https://github.com/python/cpython/issues/98778 on Python + # <= 3.9, and some 3.10 and 3.11 patch versions. + HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ()) + if sys.version_info[:2] <= (3, 11) and isinstance(exc_value, HTTPError): + self.__notes__ = None + else: + raise if exc_type and issubclass(exc_type, SyntaxError): # Handle SyntaxError's specially diff --git a/tests/test_formatting.py b/tests/test_formatting.py index 4f8b0ad..f6b9bc2 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -1,5 +1,7 @@ import sys +import traceback from typing import NoReturn +from urllib.error import HTTPError import pytest from _pytest.capture import CaptureFixture @@ -528,3 +530,9 @@ def __init__(self, name: str) -> None: print_exception(e) # does not crash output = capsys.readouterr().err assert "NamedAttributeError" in output + + +def test_works_around_httperror_bug(): + # See https://github.com/python/cpython/issues/98778 in Python <= 3.9 + err = HTTPError("url", 405, "METHOD NOT ALLOWED", None, None) + traceback.TracebackException(type(err), err, None)