[New-bugs-announce] [issue43063] zipfile.Path / importlib.resources raises KeyError if a file wasn't found

Florian Bruhin report at bugs.python.org
Fri Jan 29 11:56:11 EST 2021


New submission from Florian Bruhin <python.org at the-compiler.org>:

When a package is installed as an egg, importlib.resources.files returns a zipfile.Path rather than a pathlib.Path (maybe it returns other things too, seeing that it's documented to return a importlib.abc.Traversable - I didn't check).

However, those two have a rather odd inconsistency when it comes to reading files which don't actually exist. In that case, zipfile.Path raises KeyError rather than FileNotFoundError. After a "zip tmp/test.zip somefile":

    >>> import zipfile
    >>> p = zipfile.Path('tmp/test.zip')
    >>> (p / 'helloworld').read_text()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/lib/python3.9/zipfile.py", line 2318, in read_text
        with self.open('r', *args, **kwargs) as strm:
      File "/usr/lib/python3.9/zipfile.py", line 2306, in open
        stream = self.root.open(self.at, zip_mode, pwd=pwd)
      File "/usr/lib/python3.9/zipfile.py", line 1502, in open
        zinfo = self.getinfo(name)
      File "/usr/lib/python3.9/zipfile.py", line 1429, in getinfo
        raise KeyError(
    KeyError: "There is no item named 'helloworld' in the archive"

Note that the "zipp" backport (used by the "importlib_resources" backport) does raise FileNotFoundError instead:

    >>> import zipp
    >>> p2 = zipp.Path('tmp/test.zip')
    >>> (p2 / 'helloworld').read_text()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/lib/python3.9/site-packages/zipp.py", line 267, in read_text
        with self.open('r', *args, **kwargs) as strm:
      File "/usr/lib/python3.9/site-packages/zipp.py", line 250, in open
        raise FileNotFoundError(self)
    FileNotFoundError: tmp/test.zip/helloworld

And of course, so does pathlib.Path:

    >>> import pathlib
    >>> p3 = pathlib.Path('tmp')
    >>> (p3 / 'helloworld').read_text()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/lib/python3.9/pathlib.py", line 1255, in read_text
        with self.open(mode='r', encoding=encoding, errors=errors) as f:
      File "/usr/lib/python3.9/pathlib.py", line 1241, in open
        return io.open(self, mode, buffering, encoding, errors, newline,
      File "/usr/lib/python3.9/pathlib.py", line 1109, in _opener
        return self._accessor.open(self, flags, mode)
    FileNotFoundError: [Errno 2] No such file or directory: 'tmp/helloworld'

When using `importlib.resources.files`, this can be very surprising - especially because during testing, the package might not be installed as an egg, so this never turns up until users complain about it (which is what happened in my case - no bad feelings though!).

This seems to have been fixed by jaraco in ebbe8033b1c61854c4b623aaf9c3e170d179f875, by introducing an explicit:

    if not self.exists() and zip_mode == 'r':
        raise FileNotFoundError(self)

in open(), as part of what seems like an unrelated change (bpo-40564 / GH-22371).

While this is arguably a backwards-compatible change between 3.9 and 3.10, it might be a good idea to either adjust Python 3.9 to have the same behavior (perhaps with a new exception which inherits from both KeyError and FileNotFoundError?). At the very least, I feel like this should be documented prominently in the importlib.resources.files, importlib.abc.Traversable and zipfile.Path documentation for 3.9.

As an aside, the error message when using `.iterdir()` is similarly confusing, though that's at least consistent between the stdlib and the zipp backport:

    >>> (p / 'helloworld').iterdir()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/lib/python3.9/zipfile.py", line 2342, in iterdir
        raise ValueError("Can't listdir a file")
    ValueError: Can't listdir a file

    >>> (p2 / 'helloworld').iterdir()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/lib/python3.9/site-packages/zipp.py", line 291, in iterdir
        raise ValueError("Can't listdir a file")
    ValueError: Can't listdir a file

    >>> list((p3 / 'helloworld').iterdir())
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/lib/python3.9/pathlib.py", line 1149, in iterdir
        for name in self._accessor.listdir(self):
    FileNotFoundError: [Errno 2] No such file or directory: 'tmp/helloworld'

----------
components: Library (Lib)
messages: 385919
nosy: The Compiler, alanmcintyre, brett.cannon, jaraco, serhiy.storchaka, twouters
priority: normal
severity: normal
status: open
title: zipfile.Path / importlib.resources raises KeyError if a file wasn't found
type: behavior
versions: Python 3.9

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue43063>
_______________________________________


More information about the New-bugs-announce mailing list