Skip to content

Commit

Permalink
Implement dill serialization for ABC classes
Browse files Browse the repository at this point in the history
  • Loading branch information
emfdavid committed Oct 19, 2021
1 parent 2f56b2b commit 4cc7567
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 0 deletions.
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()

0 comments on commit 4cc7567

Please sign in to comment.