diff --git a/CHANGES.rst b/CHANGES.rst index 0983837..3ec5467 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +v3.9.0 +====== + +* #81: ``Path`` objects are now pickleable if they've been + constructed from pickleable objects. Any restored objects + will re-construct the zip file with the original arguments. + v3.8.1 ====== diff --git a/test_zipp.py b/test_zipp.py index db9c542..c3890d7 100644 --- a/test_zipp.py +++ b/test_zipp.py @@ -6,6 +6,8 @@ import tempfile import shutil import string +import pickle +import itertools import jaraco.itertools import func_timeout @@ -74,13 +76,12 @@ def temp_dir(): shutil.rmtree(tmpdir) -pass_alpharep = parameterize( - ['alpharep'], - [ - Invoked.wrap(build_alpharep_fixture), - Invoked.wrap(compose(add_dirs, build_alpharep_fixture)), - ], -) +alpharep_generators = [ + Invoked.wrap(build_alpharep_fixture), + Invoked.wrap(compose(add_dirs, build_alpharep_fixture)), +] + +pass_alpharep = parameterize(['alpharep'], alpharep_generators) class TestPath(unittest.TestCase): @@ -407,3 +408,20 @@ def test_inheritance(self, alpharep): cls = type('PathChild', (zipp.Path,), {}) 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): + print(alpharep, path_type, subpath) + zipfile_ondisk = path_type(self.zipfile_ondisk(alpharep)) + + saved_1 = pickle.dumps(zipp.Path(zipfile_ondisk, at=subpath)) + restored_1 = pickle.loads(saved_1) + first, *rest = restored_1.iterdir() + assert first.read_text().startswith('content of ') diff --git a/zipp.py b/zipp.py index 52c82a0..be1bdb0 100644 --- a/zipp.py +++ b/zipp.py @@ -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.