From bf614c91be718d4215a60c6c6b868feecefc6b72 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 8 Jun 2025 15:20:20 -0400 Subject: [PATCH 1/2] gh-135276: Refresh `zipfile.Path` from zipp 3.23 (GH-135277) Apply changes from zipp 3.23 (cherry picked from commit 8d6eb0c26276c4013346622580072908d46d2341) Co-authored-by: Jason R. Coombs --- Lib/test/test_zipfile/_path/_test_params.py | 2 +- .../test_zipfile/_path/test_complexity.py | 2 - Lib/test/test_zipfile/_path/test_path.py | 22 ++++++++--- Lib/test/test_zipfile/_path/write-alpharep.py | 1 - Lib/zipfile/_path/__init__.py | 38 ++++++++++--------- Lib/zipfile/_path/_functools.py | 20 ++++++++++ Lib/zipfile/_path/glob.py | 1 - ...-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst | 6 +++ 8 files changed, 64 insertions(+), 28 deletions(-) create mode 100644 Lib/zipfile/_path/_functools.py create mode 100644 Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst diff --git a/Lib/test/test_zipfile/_path/_test_params.py b/Lib/test/test_zipfile/_path/_test_params.py index bc95b4ebf4a168..00a9eaf2f99c1a 100644 --- a/Lib/test/test_zipfile/_path/_test_params.py +++ b/Lib/test/test_zipfile/_path/_test_params.py @@ -1,5 +1,5 @@ -import types import functools +import types from ._itertools import always_iterable diff --git a/Lib/test/test_zipfile/_path/test_complexity.py b/Lib/test/test_zipfile/_path/test_complexity.py index b505dd7c376462..7c108fc6ab8191 100644 --- a/Lib/test/test_zipfile/_path/test_complexity.py +++ b/Lib/test/test_zipfile/_path/test_complexity.py @@ -8,10 +8,8 @@ from ._functools import compose from ._itertools import consume - from ._support import import_or_skip - big_o = import_or_skip('big_o') pytest = import_or_skip('pytest') diff --git a/Lib/test/test_zipfile/_path/test_path.py b/Lib/test/test_zipfile/_path/test_path.py index aba515536f0c1a..f46b676e1ef2d8 100644 --- a/Lib/test/test_zipfile/_path/test_path.py +++ b/Lib/test/test_zipfile/_path/test_path.py @@ -1,6 +1,6 @@ +import contextlib import io import itertools -import contextlib import pathlib import pickle import stat @@ -10,12 +10,11 @@ import zipfile import zipfile._path -from test.support.os_helper import temp_dir, FakePath +from test.support.os_helper import FakePath, temp_dir from ._functools import compose from ._itertools import Counter - -from ._test_params import parameterize, Invoked +from ._test_params import Invoked, parameterize class jaraco: @@ -194,10 +193,10 @@ def test_encoding_warnings(self, alpharep): """EncodingWarning must blame the read_text and open calls.""" assert sys.flags.warn_default_encoding root = zipfile.Path(alpharep) - with self.assertWarns(EncodingWarning) as wc: + with self.assertWarns(EncodingWarning) as wc: # noqa: F821 (astral-sh/ruff#13296) root.joinpath("a.txt").read_text() assert __file__ == wc.filename - with self.assertWarns(EncodingWarning) as wc: + with self.assertWarns(EncodingWarning) as wc: # noqa: F821 (astral-sh/ruff#13296) root.joinpath("a.txt").open("r").close() assert __file__ == wc.filename @@ -365,6 +364,17 @@ def test_root_name(self, alpharep): root = zipfile.Path(alpharep) assert root.name == 'alpharep.zip' == root.filename.name + @pass_alpharep + def test_root_on_disk(self, alpharep): + """ + The name/stem of the root should match the zipfile on disk. + + This condition must hold across platforms. + """ + root = zipfile.Path(self.zipfile_ondisk(alpharep)) + assert root.name == 'alpharep.zip' == root.filename.name + assert root.stem == 'alpharep' == root.filename.stem + @pass_alpharep def test_suffix(self, alpharep): """ diff --git a/Lib/test/test_zipfile/_path/write-alpharep.py b/Lib/test/test_zipfile/_path/write-alpharep.py index 48c09b537179fd..7418391abadde5 100644 --- a/Lib/test/test_zipfile/_path/write-alpharep.py +++ b/Lib/test/test_zipfile/_path/write-alpharep.py @@ -1,4 +1,3 @@ from . import test_path - __name__ == '__main__' and test_path.build_alpharep_fixture().extractall('alpharep') diff --git a/Lib/zipfile/_path/__init__.py b/Lib/zipfile/_path/__init__.py index b7d4f8906e8bed..f44ad0794eb321 100644 --- a/Lib/zipfile/_path/__init__.py +++ b/Lib/zipfile/_path/__init__.py @@ -7,19 +7,19 @@ for more detail. """ +import functools import io -import posixpath -import zipfile import itertools -import contextlib import pathlib +import posixpath import re import stat import sys +import zipfile +from ._functools import save_method_args from .glob import Translator - __all__ = ['Path'] @@ -86,13 +86,12 @@ class InitializedState: Mix-in to save the initialization state for pickling. """ + @save_method_args def __init__(self, *args, **kwargs): - self.__args = args - self.__kwargs = kwargs super().__init__(*args, **kwargs) def __getstate__(self): - return self.__args, self.__kwargs + return self._saved___init__.args, self._saved___init__.kwargs def __setstate__(self, state): args, kwargs = state @@ -181,22 +180,27 @@ class FastLookup(CompleteDirs): """ def namelist(self): - with contextlib.suppress(AttributeError): - return self.__names - self.__names = super().namelist() - return self.__names + return self._namelist + + @functools.cached_property + def _namelist(self): + return super().namelist() def _name_set(self): - with contextlib.suppress(AttributeError): - return self.__lookup - self.__lookup = super()._name_set() - return self.__lookup + return self._name_set_prop + + @functools.cached_property + def _name_set_prop(self): + return super()._name_set() def _extract_text_encoding(encoding=None, *args, **kwargs): # compute stack level so that the caller of the caller sees any warning. is_pypy = sys.implementation.name == 'pypy' - stack_level = 3 + is_pypy + # PyPy no longer special cased after 7.3.19 (or maybe 7.3.18) + # See jaraco/zipp#143 + is_old_pypi = is_pypy and sys.pypy_version_info < (7, 3, 19) + stack_level = 3 + is_old_pypi return io.text_encoding(encoding, stack_level), args, kwargs @@ -351,7 +355,7 @@ def open(self, mode='r', *args, pwd=None, **kwargs): return io.TextIOWrapper(stream, encoding, *args, **kwargs) def _base(self): - return pathlib.PurePosixPath(self.at or self.root.filename) + return pathlib.PurePosixPath(self.at) if self.at else self.filename @property def name(self): diff --git a/Lib/zipfile/_path/_functools.py b/Lib/zipfile/_path/_functools.py new file mode 100644 index 00000000000000..7390be21873e4b --- /dev/null +++ b/Lib/zipfile/_path/_functools.py @@ -0,0 +1,20 @@ +import collections +import functools + + +# from jaraco.functools 4.0.2 +def save_method_args(method): + """ + Wrap a method such that when it is called, the args and kwargs are + saved on the method. + """ + args_and_kwargs = collections.namedtuple('args_and_kwargs', 'args kwargs') # noqa: PYI024 + + @functools.wraps(method) + def wrapper(self, /, *args, **kwargs): + attr_name = '_saved_' + method.__name__ + attr = args_and_kwargs(args, kwargs) + setattr(self, attr_name, attr) + return method(self, *args, **kwargs) + + return wrapper diff --git a/Lib/zipfile/_path/glob.py b/Lib/zipfile/_path/glob.py index 4320f1c0badcf9..4ed74cc48d9a91 100644 --- a/Lib/zipfile/_path/glob.py +++ b/Lib/zipfile/_path/glob.py @@ -1,7 +1,6 @@ import os import re - _default_seps = os.sep + str(os.altsep) * bool(os.altsep) diff --git a/Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst b/Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst new file mode 100644 index 00000000000000..a8fbd48d08aa37 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst @@ -0,0 +1,6 @@ +Synchronized zipfile.Path with zipp 3.23, including improved performance of +:meth:`zipfile.Path.open` for non-reading modes, rely on +:func:`functools.cached_property` to cache values on the instance. Rely on +``save_method_args`` to save the initialization method arguments. Fixed +``.name``, ``.stem`` and other basename-based properties on Windows when +working with a zipfile on disk. From d79501c202e3895a8e5328c56f2e1e7a6c55cfa9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 8 Jun 2025 15:39:53 -0400 Subject: [PATCH 2/2] Removed features slated for Python 3.15 only. --- Lib/zipfile/_path/__init__.py | 27 +++++++++---------- Lib/zipfile/_path/_functools.py | 20 -------------- ...-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst | 5 +--- 3 files changed, 13 insertions(+), 39 deletions(-) delete mode 100644 Lib/zipfile/_path/_functools.py diff --git a/Lib/zipfile/_path/__init__.py b/Lib/zipfile/_path/__init__.py index f44ad0794eb321..02f81171b4f11b 100644 --- a/Lib/zipfile/_path/__init__.py +++ b/Lib/zipfile/_path/__init__.py @@ -7,7 +7,7 @@ for more detail. """ -import functools +import contextlib import io import itertools import pathlib @@ -17,7 +17,6 @@ import sys import zipfile -from ._functools import save_method_args from .glob import Translator __all__ = ['Path'] @@ -86,12 +85,13 @@ class InitializedState: Mix-in to save the initialization state for pickling. """ - @save_method_args def __init__(self, *args, **kwargs): + self.__args = args + self.__kwargs = kwargs super().__init__(*args, **kwargs) def __getstate__(self): - return self._saved___init__.args, self._saved___init__.kwargs + return self.__args, self.__kwargs def __setstate__(self, state): args, kwargs = state @@ -180,19 +180,16 @@ class FastLookup(CompleteDirs): """ def namelist(self): - return self._namelist - - @functools.cached_property - def _namelist(self): - return super().namelist() + with contextlib.suppress(AttributeError): + return self.__names + self.__names = super().namelist() + return self.__names def _name_set(self): - return self._name_set_prop - - @functools.cached_property - def _name_set_prop(self): - return super()._name_set() - + with contextlib.suppress(AttributeError): + return self.__lookup + self.__lookup = super()._name_set() + return self.__lookup def _extract_text_encoding(encoding=None, *args, **kwargs): # compute stack level so that the caller of the caller sees any warning. diff --git a/Lib/zipfile/_path/_functools.py b/Lib/zipfile/_path/_functools.py deleted file mode 100644 index 7390be21873e4b..00000000000000 --- a/Lib/zipfile/_path/_functools.py +++ /dev/null @@ -1,20 +0,0 @@ -import collections -import functools - - -# from jaraco.functools 4.0.2 -def save_method_args(method): - """ - Wrap a method such that when it is called, the args and kwargs are - saved on the method. - """ - args_and_kwargs = collections.namedtuple('args_and_kwargs', 'args kwargs') # noqa: PYI024 - - @functools.wraps(method) - def wrapper(self, /, *args, **kwargs): - attr_name = '_saved_' + method.__name__ - attr = args_and_kwargs(args, kwargs) - setattr(self, attr_name, attr) - return method(self, *args, **kwargs) - - return wrapper diff --git a/Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst b/Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst index a8fbd48d08aa37..e630b7d671a18e 100644 --- a/Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst +++ b/Misc/NEWS.d/next/Library/2025-06-08-14-50-34.gh-issue-135276.ZLUhV1.rst @@ -1,6 +1,3 @@ -Synchronized zipfile.Path with zipp 3.23, including improved performance of -:meth:`zipfile.Path.open` for non-reading modes, rely on -:func:`functools.cached_property` to cache values on the instance. Rely on -``save_method_args`` to save the initialization method arguments. Fixed +Backported bugfixes in zipfile.Path from zipp 3.23. Fixed ``.name``, ``.stem`` and other basename-based properties on Windows when working with a zipfile on disk.