mirror of
https://github.com/python/cpython.git
synced 2025-08-08 10:58:51 +00:00
gh-98108: Add limited pickleability to zipfile.Path (GH-98109)
* gh-98098: Move zipfile into a package. * Moved test_zipfile to a package * Extracted module for test_path. * Add blurb * Add jaraco as owner of zipfile.Path. * Synchronize with minor changes found at jaraco/zipp@d9e7f4352d. * gh-98108: Sync with zipp 3.9.1 adding pickleability.
This commit is contained in:
parent
5f8898216e
commit
93f22d30eb
6 changed files with 110 additions and 22 deletions
9
Lib/test/test_zipfile/_functools.py
Normal file
9
Lib/test/test_zipfile/_functools.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import functools
|
||||||
|
|
||||||
|
|
||||||
|
# from jaraco.functools 3.5.2
|
||||||
|
def compose(*funcs):
|
||||||
|
def compose_two(f1, f2):
|
||||||
|
return lambda *args, **kwargs: f1(f2(*args, **kwargs))
|
||||||
|
|
||||||
|
return functools.reduce(compose_two, funcs)
|
12
Lib/test/test_zipfile/_itertools.py
Normal file
12
Lib/test/test_zipfile/_itertools.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# from more_itertools v8.13.0
|
||||||
|
def always_iterable(obj, base_type=(str, bytes)):
|
||||||
|
if obj is None:
|
||||||
|
return iter(())
|
||||||
|
|
||||||
|
if (base_type is not None) and isinstance(obj, base_type):
|
||||||
|
return iter((obj,))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return iter(obj)
|
||||||
|
except TypeError:
|
||||||
|
return iter((obj,))
|
39
Lib/test/test_zipfile/_test_params.py
Normal file
39
Lib/test/test_zipfile/_test_params.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import types
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from ._itertools import always_iterable
|
||||||
|
|
||||||
|
|
||||||
|
def parameterize(names, value_groups):
|
||||||
|
"""
|
||||||
|
Decorate a test method to run it as a set of subtests.
|
||||||
|
|
||||||
|
Modeled after pytest.parametrize.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapped(self):
|
||||||
|
for values in value_groups:
|
||||||
|
resolved = map(Invoked.eval, always_iterable(values))
|
||||||
|
params = dict(zip(always_iterable(names), resolved))
|
||||||
|
with self.subTest(**params):
|
||||||
|
func(self, **params)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
class Invoked(types.SimpleNamespace):
|
||||||
|
"""
|
||||||
|
Wrap a function to be invoked for each usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wrap(cls, func):
|
||||||
|
return cls(func=func)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def eval(cls, cand):
|
||||||
|
return cand.func() if isinstance(cand, cls) else cand
|
|
@ -4,7 +4,12 @@ import contextlib
|
||||||
import pathlib
|
import pathlib
|
||||||
import unittest
|
import unittest
|
||||||
import string
|
import string
|
||||||
import functools
|
import pickle
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from ._test_params import parameterize, Invoked
|
||||||
|
from ._functools import compose
|
||||||
|
|
||||||
|
|
||||||
from test.support.os_helper import temp_dir
|
from test.support.os_helper import temp_dir
|
||||||
|
|
||||||
|
@ -76,18 +81,12 @@ def build_alpharep_fixture():
|
||||||
return zf
|
return zf
|
||||||
|
|
||||||
|
|
||||||
def pass_alpharep(meth):
|
alpharep_generators = [
|
||||||
"""
|
Invoked.wrap(build_alpharep_fixture),
|
||||||
Given a method, wrap it in a for loop that invokes method
|
Invoked.wrap(compose(add_dirs, build_alpharep_fixture)),
|
||||||
with each subtest.
|
]
|
||||||
"""
|
|
||||||
|
|
||||||
@functools.wraps(meth)
|
pass_alpharep = parameterize(['alpharep'], alpharep_generators)
|
||||||
def wrapper(self):
|
|
||||||
for alpharep in self.zipfile_alpharep():
|
|
||||||
meth(self, alpharep=alpharep)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class TestPath(unittest.TestCase):
|
class TestPath(unittest.TestCase):
|
||||||
|
@ -95,12 +94,6 @@ class TestPath(unittest.TestCase):
|
||||||
self.fixtures = contextlib.ExitStack()
|
self.fixtures = contextlib.ExitStack()
|
||||||
self.addCleanup(self.fixtures.close)
|
self.addCleanup(self.fixtures.close)
|
||||||
|
|
||||||
def zipfile_alpharep(self):
|
|
||||||
with self.subTest():
|
|
||||||
yield build_alpharep_fixture()
|
|
||||||
with self.subTest():
|
|
||||||
yield add_dirs(build_alpharep_fixture())
|
|
||||||
|
|
||||||
def zipfile_ondisk(self, alpharep):
|
def zipfile_ondisk(self, alpharep):
|
||||||
tmpdir = pathlib.Path(self.fixtures.enter_context(temp_dir()))
|
tmpdir = pathlib.Path(self.fixtures.enter_context(temp_dir()))
|
||||||
buffer = alpharep.fp
|
buffer = alpharep.fp
|
||||||
|
@ -418,6 +411,21 @@ class TestPath(unittest.TestCase):
|
||||||
@pass_alpharep
|
@pass_alpharep
|
||||||
def test_inheritance(self, alpharep):
|
def test_inheritance(self, alpharep):
|
||||||
cls = type('PathChild', (zipfile.Path,), {})
|
cls = type('PathChild', (zipfile.Path,), {})
|
||||||
for alpharep in self.zipfile_alpharep():
|
|
||||||
file = cls(alpharep).joinpath('some dir').parent
|
file = cls(alpharep).joinpath('some dir').parent
|
||||||
assert isinstance(file, cls)
|
assert isinstance(file, cls)
|
||||||
|
|
||||||
|
@parameterize(
|
||||||
|
['alpharep', 'path_type', 'subpath'],
|
||||||
|
itertools.product(
|
||||||
|
alpharep_generators,
|
||||||
|
[str, pathlib.Path],
|
||||||
|
['', 'b/'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_pickle(self, alpharep, path_type, subpath):
|
||||||
|
zipfile_ondisk = path_type(self.zipfile_ondisk(alpharep))
|
||||||
|
|
||||||
|
saved_1 = pickle.dumps(zipfile.Path(zipfile_ondisk, at=subpath))
|
||||||
|
restored_1 = pickle.loads(saved_1)
|
||||||
|
first, *rest = restored_1.iterdir()
|
||||||
|
assert first.read_text().startswith('content of ')
|
||||||
|
|
|
@ -62,7 +62,25 @@ def _difference(minuend, subtrahend):
|
||||||
return itertools.filterfalse(set(subtrahend).__contains__, minuend)
|
return itertools.filterfalse(set(subtrahend).__contains__, minuend)
|
||||||
|
|
||||||
|
|
||||||
class CompleteDirs(zipfile.ZipFile):
|
class InitializedState:
|
||||||
|
"""
|
||||||
|
Mix-in to save the initialization state for pickling.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.__args = args
|
||||||
|
self.__kwargs = kwargs
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return self.__args, self.__kwargs
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
args, kwargs = state
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CompleteDirs(InitializedState, zipfile.ZipFile):
|
||||||
"""
|
"""
|
||||||
A ZipFile subclass that ensures that implied directories
|
A ZipFile subclass that ensures that implied directories
|
||||||
are always included in the namelist.
|
are always included in the namelist.
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
``zipfile.Path`` is now pickleable if its initialization parameters were
|
||||||
|
pickleable (e.g. for file system paths).
|
Loading…
Add table
Add a link
Reference in a new issue