mirror of
https://github.com/python/cpython
synced 2024-09-16 02:19:59 +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
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 pathlib
|
||||
import unittest
|
||||
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
|
||||
|
||||
|
@ -76,18 +81,12 @@ def build_alpharep_fixture():
|
|||
return zf
|
||||
|
||||
|
||||
def pass_alpharep(meth):
|
||||
"""
|
||||
Given a method, wrap it in a for loop that invokes method
|
||||
with each subtest.
|
||||
"""
|
||||
alpharep_generators = [
|
||||
Invoked.wrap(build_alpharep_fixture),
|
||||
Invoked.wrap(compose(add_dirs, build_alpharep_fixture)),
|
||||
]
|
||||
|
||||
@functools.wraps(meth)
|
||||
def wrapper(self):
|
||||
for alpharep in self.zipfile_alpharep():
|
||||
meth(self, alpharep=alpharep)
|
||||
|
||||
return wrapper
|
||||
pass_alpharep = parameterize(['alpharep'], alpharep_generators)
|
||||
|
||||
|
||||
class TestPath(unittest.TestCase):
|
||||
|
@ -95,12 +94,6 @@ def setUp(self):
|
|||
self.fixtures = contextlib.ExitStack()
|
||||
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):
|
||||
tmpdir = pathlib.Path(self.fixtures.enter_context(temp_dir()))
|
||||
buffer = alpharep.fp
|
||||
|
@ -418,6 +411,21 @@ def test_root_unnamed(self, alpharep):
|
|||
@pass_alpharep
|
||||
def test_inheritance(self, alpharep):
|
||||
cls = type('PathChild', (zipfile.Path,), {})
|
||||
for alpharep in self.zipfile_alpharep():
|
||||
file = cls(alpharep).joinpath('some dir').parent
|
||||
assert isinstance(file, cls)
|
||||
file = cls(alpharep).joinpath('some dir').parent
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
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…
Reference in a new issue