Skip to content

zipfile.Path.is_symlink() raises KeyError for the archive root #153529

Description

@whyvineet

Bug report

Bug description:

zipfile.Path.is_symlink() raises a KeyError when used on the archive root. The root is a valid Path object, and other predicate methods do not raise errors in this case.

Reproducer:

import io
import zipfile

data = io.BytesIO()

with zipfile.ZipFile(data, "w") as zf:
    zf.writestr("a.txt", "hello")

data.seek(0)

with zipfile.ZipFile(data) as zf:
    root = zipfile.Path(zf)

    print(root.is_dir())
    print(root.is_file())
    print(root.is_symlink())

Output:

True
False
Traceback (most recent call last):
  File "d:\Open Source\cpython\repro.py", line 16, in <module>
    print(root.is_symlink())
          ~~~~~~~~~~~~~~~^^
  File "C:\Users\vinee\AppData\Local\Programs\Python\Python315\Lib\zipfile\_path\__init__.py", line 417, in is_symlink
    info = self.root.getinfo(self.at)
  File "C:\Users\vinee\AppData\Local\Programs\Python\Python315\Lib\zipfile\_path\__init__.py", line 140, in getinfo
    return super().getinfo(name)
           ~~~~~~~~~~~~~~~^^^^^^
  File "C:\Users\vinee\AppData\Local\Programs\Python\Python315\Lib\zipfile\__init__.py", line 1646, in getinfo
    raise KeyError(
        'There is no item named %r in the archive' % name)
KeyError: "There is no item named '' in the archive"

Path.is_symlink() unconditionally calls self.root.getinfo(self.at). For the archive root, self.at == "", so ZipFile.getinfo("") raises KeyError. Unlike is_dir(), is_symlink() does not special-case the root.

Since the archive root is conceptually a directory rather than a symlink, I would expect:

root.is_symlink() == False

This change would make its behavior match the other predicate methods, like is_dir() and is_file(), which do not raise errors when called on the archive root.

A minimal fix appears to be:

def is_symlink(self):
    if not self.at:
        return False

    info = self.root.getinfo(self.at)
    mode = info.external_attr >> 16
    return stat.S_ISLNK(mode)

I also noticed there is currently no regression test covering root.is_symlink().

CPython versions tested on:

3.15

Operating systems tested on:

Windows

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-bugAn unexpected behavior, bug, or error

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions