diff --git a/dill/_dill.py b/dill/_dill.py index 4dfdb7e3..b735c8e2 100644 --- a/dill/_dill.py +++ b/dill/_dill.py @@ -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 @@ -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 '' 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 diff --git a/tests/test_abc.py b/tests/test_abc.py new file mode 100644 index 00000000..26e07052 --- /dev/null +++ b/tests/test_abc.py @@ -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 + # + # .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()