diff --git a/Lib/test/test_zipfile/_path/test_path.py b/Lib/test/test_zipfile/_path/test_path.py index 351d9eefeb09a51..764f34060be7fd1 100644 --- a/Lib/test/test_zipfile/_path/test_path.py +++ b/Lib/test/test_zipfile/_path/test_path.py @@ -351,6 +351,21 @@ class Subclass(zipfile.Path): root = Subclass(alpharep) assert isinstance(root / 'b', Subclass) + def test_at(self): + zip_file = self.fixtures.enter_context(zipfile.ZipFile(io.BytesIO(), 'w')) + root = zipfile.Path(zip_file) + assert root.at == '' + assert (root / 'foo' / 'bar').at == 'foo/bar' + + def test_str(self): + """ + An unnamed zip file is indicated by ":zipfile:" + """ + zip_file = self.fixtures.enter_context(zipfile.ZipFile(io.BytesIO(), 'w')) + root = zipfile.Path(zip_file) + assert str(root) == ':zipfile:' + assert str(root / 'foo' / 'bar') == ':zipfile:/foo/bar' + @pass_alpharep def test_filename(self, alpharep): root = zipfile.Path(alpharep) diff --git a/Lib/zipfile/_path/__init__.py b/Lib/zipfile/_path/__init__.py index 80f5d607731fd78..ac86f2e809896d9 100644 --- a/Lib/zipfile/_path/__init__.py +++ b/Lib/zipfile/_path/__init__.py @@ -273,7 +273,7 @@ class Path: resolve to the zipfile. >>> str(path) - 'mem/abcde.zip/' + 'mem/abcde.zip' >>> path.name 'abcde.zip' >>> path.filename == pathlib.Path('mem/abcde.zip') @@ -431,7 +431,10 @@ def relative_to(self, other, *extra): return posixpath.relpath(str(self), str(other.joinpath(*extra))) def __str__(self): - return posixpath.join(self.root.filename, self.at) + root = self.root.filename + if root is None: + root = ':zipfile:' + return posixpath.join(root, self.at) if self.at else root def __repr__(self): return self.__repr.format(self=self) diff --git a/Misc/NEWS.d/next/Library/2026-10-21-19-40-00.gh-issue-100609.THXgRC.rst b/Misc/NEWS.d/next/Library/2026-10-21-19-40-00.gh-issue-100609.THXgRC.rst new file mode 100644 index 000000000000000..4a793a0fdcd91e8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-21-19-40-00.gh-issue-100609.THXgRC.rst @@ -0,0 +1,5 @@ +``str()`` of a :class:`zipfile.Path` whose underlying zipfile has no +filename, such as one constructed from an :class:`io.BytesIO`, now renders +that filename as ``:zipfile:`` instead of raising :exc:`TypeError`. The root +of a zipfile is also no longer rendered with a trailing slash. Synchronized +with :pypi:`zipp` 3.23.1.