Skip to content

urllib.error.HTTPError(..., fp=None) raises a KeyError instead of an AttributeError on attribute access #98778

Closed
@vxgmichel

Description

@vxgmichel

Bug report

The exception urllib.error.HTTPError(..., fp=None) raises a KeyError instead of an AttributeError when accessing an attribute that does not exist.

>>> from urllib.error import HTTPError >>> x = HTTPError("url", 405, "METHOD NOT ALLOWED", None, None) >>> assert getattr(x, "__notes__", ()) == () Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/vinmic/repos/cpython/Lib/tempfile.py", line 477, in __getattr__ file = self.__dict__['file'] ~~~~~~~~~~~~~^^^^^^^^ KeyError: 'file'

Your environment

Python 3.12.0a1+ (heads/main:bded5edd9a, Oct 27 2022, 19:23:58) [GCC 11.3.0] on linux 

This bug should be reproducible for all python3 versions on all systems.

Context

I found this error while running a code similar to this:

from logging import getLogger from urllib.error import HTTPError try: raise HTTPError("url", 405, "METHOD NOT ALLOWED", None, None) except Exception: getLogger().exception("Ooops")

Instead of having the exception logged, I ended up with the following trace:

--- Logging error --- Traceback (most recent call last): File "/home/vinmic/repos/cpython/../test_trio/test_trio.py", line 6, in <module> raise HTTPError("url", 405, "METHOD NOT ALLOWED", None, None) urllib.error.HTTPError: HTTP Error 405: METHOD NOT ALLOWED During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 1160, in emit msg = self.format(record) ^^^^^^^^^^^^^^^^^^^ File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 999, in format return fmt.format(record) ^^^^^^^^^^^^^^^^^^ File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 711, in format record.exc_text = self.formatException(record.exc_info) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 661, in formatException traceback.print_exception(ei[0], ei[1], tb, None, sio) File "/home/vinmic/repos/cpython/Lib/traceback.py", line 124, in print_exception te = TracebackException(type(value), value, tb, limit=limit, compact=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/vinmic/repos/cpython/Lib/traceback.py", line 697, in __init__ self.__notes__ = getattr(exc_value, '__notes__', None) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/vinmic/repos/cpython/Lib/tempfile.py", line 477, in __getattr__ file = self.__dict__['file'] ~~~~~~~~~~~~~^^^^^^^^ KeyError: 'file' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/home/vinmic/repos/cpython/../test_trio/test_trio.py", line 8, in <module> getLogger().exception("Ooops") File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 1574, in exception self.error(msg, *args, exc_info=exc_info, **kwargs) File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 1568, in error self._log(ERROR, msg, args, **kwargs) File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 1684, in _log self.handle(record) File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 1700, in handle self.callHandlers(record) File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 1770, in callHandlers lastResort.handle(record) File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 1028, in handle self.emit(record) File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 1168, in emit self.handleError(record) File "/home/vinmic/repos/cpython/Lib/logging/__init__.py", line 1082, in handleError traceback.print_exception(t, v, tb, None, sys.stderr) File "/home/vinmic/repos/cpython/Lib/traceback.py", line 124, in print_exception te = TracebackException(type(value), value, tb, limit=limit, compact=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/vinmic/repos/cpython/Lib/traceback.py", line 763, in __init__ context = TracebackException( ^^^^^^^^^^^^^^^^^^^ File "/home/vinmic/repos/cpython/Lib/traceback.py", line 697, in __init__ self.__notes__ = getattr(exc_value, '__notes__', None) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/vinmic/repos/cpython/Lib/tempfile.py", line 477, in __getattr__ file = self.__dict__['file'] ~~~~~~~~~~~~~^^^^^^^^ KeyError: 'file' 

Note however that the exception is logged properly on python 3.9 (without the exceptiongroup module imported). So maybe the following patch should be applied on top of HTTPError being fixed in order to make tracebacks more robust:

diff --git a/Lib/traceback.py b/Lib/traceback.py index 6270100348..a9c15d59be 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -694,7 +694,10 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, # 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 Exception: + self.__notes__ = None if exc_type and issubclass(exc_type, SyntaxError): # Handle SyntaxError's specially

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions