Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create lazy loading lookup class to support pickling #82

Merged
merged 12 commits into from Oct 8, 2022
21 changes: 21 additions & 0 deletions test_zipp.py
Expand Up @@ -7,6 +7,7 @@
import shutil
import string
import functools
import pickle

import jaraco.itertools
import func_timeout
Expand Down Expand Up @@ -416,3 +417,23 @@ def test_inheritance(self, alpharep):
for alpharep in self.zipfile_alpharep():
file = cls(alpharep).joinpath('some dir').parent
assert isinstance(file, cls)

def test_can_pickle_string_path(self):
path_1 = zipp.Path("/path/to/a/file.zip")
jaraco marked this conversation as resolved.
Show resolved Hide resolved
path_1_pickle = pickle.dumps(path_1)
path_1_load = pickle.loads(path_1_pickle)
assert path_1.root == path_1_load.root
path_2 = zipp.Path("/path/to/a/file.zip", at="something.txt")
path_2_pickle = pickle.dumps(path_2)
path_2_load = pickle.loads(path_2_pickle)
assert path_2.root == path_2_load.root

def test_can_pickle_pathlib_path(self):
path_1 = zipp.Path(pathlib.Path("/path/to/a/file.zip"))
path_1_pickle = pickle.dumps(path_1)
path_1_load = pickle.loads(path_1_pickle)
assert path_1.root == path_1_load.root
path_2 = zipp.Path(pathlib.Path("/path/to/a/file.zip", at="something.txt"))
jaraco marked this conversation as resolved.
Show resolved Hide resolved
path_2_pickle = pickle.dumps(path_2)
path_2_load = pickle.loads(path_2_pickle)
assert path_2.root == path_2_load.root
65 changes: 64 additions & 1 deletion zipp.py
Expand Up @@ -61,6 +61,39 @@ def _difference(minuend, subtrahend):
"""
return itertools.filterfalse(set(subtrahend).__contains__, minuend)

class PickleableClass():
jaraco marked this conversation as resolved.
Show resolved Hide resolved
jaraco marked this conversation as resolved.
Show resolved Hide resolved
"""
Utility object that wraps another un-pickleable object that,
for example, might hold a file object, and saves the
initialization parameters. When pickeled, the un-pickleable
object is discarded, and when loaded, it is rebuilt from the
initialization params."""
_class = object

def __init__(self, *args, **kwargs):
self._args = args
self._kwargs = kwargs
self._obj = None

def __getattr__(self, name):
if self._obj is None:
self._obj = self._class(*self._args, **self._kwargs)
return getattr(self._obj, name)

def __getstate__(self):
state = self.__dict__.copy()
state['_obj'] = None
return state

def __setstate__(self, state):
self.__dict__.update(state)

def __repr__(self):
return f"{self.__class__.__name__}(class={self._class}, args={self._args}, kwargs={self._kwargs})"

def __eq__(self, other):
return self._class == other._class and self._args == other._args and self._kwargs == other._kwargs


class CompleteDirs(zipfile.ZipFile):
"""
Expand Down Expand Up @@ -129,6 +162,13 @@ def _name_set(self):
self.__lookup = super(FastLookup, self)._name_set()
return self.__lookup

class PickleableFastLookup(PickleableClass):
_class = FastLookup

@property
def filename(self):
return self._args[0]


class Path:
"""
Expand Down Expand Up @@ -220,9 +260,32 @@ def __init__(self, root, at=""):
original type, the caller should either create a
separate ZipFile object or pass a filename.
"""
self.root = FastLookup.make(root)
self.root = self._make_root(root)
self.at = at

def _make_root(self, source):
jaraco marked this conversation as resolved.
Show resolved Hide resolved
"""
Given a source (filename or zipfile), return an
appropriate root object.
"""
if isinstance(source, CompleteDirs):
return source

if isinstance(source, (str, pathlib.Path)):
return PickleableFastLookup(source)

if not isinstance(source, zipfile.ZipFile):
return FastLookup(source)

# Only allow for FastLookup when supplied zipfile is read-only
if 'r' not in source.mode:
subcls = CompleteDirs
else:
subcls = FastLookup

source.__class__ = subcls
return source

def open(self, mode='r', *args, pwd=None, **kwargs):
"""
Open this entry as text or binary following the semantics
Expand Down