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

Issue 332 abc data #427

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions dill/_dill.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def _trace(boolean):
import marshal
import gc
# import zlib
import abc
from weakref import ReferenceType, ProxyType, CallableProxyType
from functools import partial
from operator import itemgetter, attrgetter
Expand Down Expand Up @@ -1456,6 +1457,27 @@ def save_module(pickler, obj):
return
return

@register(abc.ABCMeta)
def save_abc(pickler, obj):
"""Use StockePickler to ignore ABC internal state which should not be serialized"""

name = getattr(obj, '__qualname__', getattr(obj, '__name__', None))
if '<locals>' in name or obj.__module__ != '__main__':
log.info("C2: %s" % obj)
_dict = _dict_from_dictproxy(obj.__dict__)
(registry, _, _, _) = abc._get_dump(obj)
_dict["_abc_impl"] = [reg() for reg in registry]
pickler.save_reduce(
_create_type,
(type(obj), obj.__name__, obj.__bases__, _dict),
obj=obj
)
log.info("# C2")
else:
log.info("C1: %s" % obj)
StockPickler.save_type(pickler, obj)
log.info("# C1")

@register(TypeType)
def save_type(pickler, obj):
#stack[id(obj)] = len(stack), obj #XXX: probably don't obj in all cases below
Expand Down
143 changes: 143 additions & 0 deletions tests/test_abc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#!/usr/bin/env python
"""
test dill's ability to pickle abstract base class objects
"""
import dill as pickle
import abc

from types import FunctionType

pickle.settings['recurse'] = True

class OneTwoThree(abc.ABC):
@abc.abstractmethod
def foo(self):
"""A method"""
pass

@property
@abc.abstractmethod
def bar(self):
"""Property getter"""
pass

@bar.setter
@abc.abstractmethod
def bar(self, value):
"""Property setter"""
pass

@classmethod
@abc.abstractmethod
def cfoo(cls):
"""Class method"""
pass

@staticmethod
@abc.abstractmethod
def sfoo():
"""Static method"""
pass

class EasyAsAbc(OneTwoThree):
def __init__(self):
self._bar = None

def foo(self):
return "Instance Method FOO"

@property
def bar(self):
return self._bar

@bar.setter
def bar(self, value):
self._bar = value

@classmethod
def cfoo(cls):
return "Class Method CFOO"

@staticmethod
def sfoo():
return "Static Method SFOO"

def test_abc_non_local():
assert pickle.loads(pickle.dumps(OneTwoThree)) == OneTwoThree
assert pickle.loads(pickle.dumps(EasyAsAbc)) == EasyAsAbc
instance = EasyAsAbc()
# Set a property that StockPickle can't preserve
instance.bar = lambda x: x**2
depickled = pickle.loads(pickle.dumps(instance))
assert type(depickled) == type(instance)
assert type(depickled.bar) == FunctionType
assert depickled.bar(3) == 9
assert depickled.sfoo() == "Static Method SFOO"
assert depickled.cfoo() == "Class Method CFOO"
assert depickled.foo() == "Instance Method FOO"
print("Tada")

def test_abc_local():
"""
Test using locally scoped ABC class
"""
class LocalABC(abc.ABC):
@abc.abstractmethod
def foo(self):
pass

res = pickle.dumps(LocalABC)
pik = pickle.loads(res)
assert type(pik) == type(LocalABC)
# TODO should work like it does for non local classes
# <class '__main__.LocalABC'>
# <class '__main__.test_abc_local.<locals>.LocalABC'>

class Real(pik):
def foo(self):
return "True!"

real = Real()
assert real.foo() == "True!"

try:
pik()
except TypeError as e:
print("Tada: ", e)
else:
print('Failed to raise type error')
assert False

def test_meta_local_no_cache():
"""
Test calling metaclass and cache registration
"""
LocalMetaABC = abc.ABCMeta('LocalMetaABC', (), {})

class ClassyClass:
pass

class KlassyClass:
pass

LocalMetaABC.register(ClassyClass)

assert not issubclass(KlassyClass, LocalMetaABC)
assert issubclass(ClassyClass, LocalMetaABC)

res = pickle.dumps(LocalMetaABC)
assert b"ClassyClass" in res
assert b"KlassyClass" not in res

pik = pickle.loads(res)
assert type(pik) == type(LocalMetaABC)

pik.register(ClassyClass) # TODO: test should pass without calling register again
assert not issubclass(KlassyClass, pik)
assert issubclass(ClassyClass, pik)
print("tada")

if __name__ == '__main__':
test_abc_non_local()
test_abc_local()
test_meta_local_no_cache()