Skip to content

Commit bfccf0f

Browse files
miss-islingtonjaracoclaude
authored
[3.15] gh-100609: Render an unnamed zipfile as ":zipfile:" in zipfile.Path (GH-160103) (#160118)
gh-100609: Render an unnamed zipfile as ":zipfile:" in `zipfile.Path` (GH-160103) 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 ba96fab) Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 7e102ec commit bfccf0f

4 files changed

Lines changed: 35 additions & 3 deletions

File tree

‎Lib/test/test_zipfile/_path/test_path.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,21 @@ class Subclass(zipfile.Path):
351351
root = Subclass(alpharep)
352352
assert isinstance(root / 'b', Subclass)
353353

354+
def test_at(self):
355+
zip_file = self.enterContext(zipfile.ZipFile(io.BytesIO(), 'w'))
356+
root = zipfile.Path(zip_file)
357+
assert root.at == ''
358+
assert (root / 'foo' / 'bar').at == 'foo/bar'
359+
360+
def test_str(self):
361+
"""
362+
An unnamed zip file is indicated by ":zipfile:"
363+
"""
364+
zip_file = self.enterContext(zipfile.ZipFile(io.BytesIO(), 'w'))
365+
root = zipfile.Path(zip_file)
366+
assert str(root) == ':zipfile:'
367+
assert str(root / 'foo' / 'bar') == ':zipfile:/foo/bar'
368+
354369
@pass_alpharep
355370
def test_filename(self, alpharep):
356371
root = zipfile.Path(alpharep)

‎Lib/zipfile/_path/__init__.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import sys
1818
import zipfile
1919

20-
from ._functools import save_method_args
20+
from ._functools import none_as, save_method_args
2121
from .glob import Translator
2222

2323
__all__ = ['Path']
@@ -276,7 +276,7 @@ class Path:
276276
resolve to the zipfile.
277277
278278
>>> str(path)
279-
'mem/abcde.zip/'
279+
'mem/abcde.zip'
280280
>>> path.name
281281
'abcde.zip'
282282
>>> path.filename == pathlib.Path('mem/abcde.zip')
@@ -434,7 +434,8 @@ def relative_to(self, other, *extra):
434434
return posixpath.relpath(str(self), str(other.joinpath(*extra)))
435435

436436
def __str__(self):
437-
return posixpath.join(self.root.filename, self.at)
437+
root = none_as(self.root.filename, ':zipfile:')
438+
return posixpath.join(root, self.at) if self.at else root
438439

439440
def __repr__(self):
440441
return self.__repr.format(self=self)

‎Lib/zipfile/_path/_functools.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,14 @@ def wrapper(self, /, *args, **kwargs):
1818
return method(self, *args, **kwargs)
1919

2020
return wrapper
21+
22+
23+
# from jaraco.functools 4.3
24+
def none_as(value, replacement=None):
25+
"""
26+
>>> none_as(None, 'foo')
27+
'foo'
28+
>>> none_as('bar', 'foo')
29+
'bar'
30+
"""
31+
return replacement if value is None else value
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
``str()`` of a :class:`zipfile.Path` whose underlying zipfile has no
2+
filename, such as one constructed from an :class:`io.BytesIO`, now renders
3+
that filename as ``:zipfile:`` instead of raising :exc:`TypeError`. The root
4+
of a zipfile is also no longer rendered with a trailing slash. Synchronized
5+
with :pypi:`zipp` 3.23.1.

0 commit comments

Comments
 (0)