https://github.com/python/cpython/commit/ba0e4a410552652bf7ca28ef23552111671c8e02
commit: ba0e4a410552652bf7ca28ef23552111671c8e02
branch: 3.14
author: Jason R. Coombs <[email protected]>
committer: jaraco <[email protected]>
date: 2026-10-21T23:10:39Z
summary:

[3.14] gh-100609: Render an unnamed zipfile as ":zipfile:" in `zipfile.Path` 
(GH-160103) (#160124)

Synchronizes the fix from zipp 3.23.1. `str()` of a `zipfile.Path` whose
zipfile has no filename previously raised `TypeError`.
(cherry picked from commit ba96fab7348310651dcabadc1f4e954949e0e05a)

Co-authored-by: Claude Opus 5 <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-10-21-19-40-00.gh-issue-100609.THXgRC.rst
M Lib/test/test_zipfile/_path/test_path.py
M Lib/zipfile/_path/__init__.py

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.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to