Skip to content

Commit

Permalink
Merge pull request #450 from jrbourbeau/abstracts
Browse files Browse the repository at this point in the history
Add support for `abc.abstract*` methods
  • Loading branch information
jakirkham committed Nov 29, 2021
2 parents 7ddb089 + 2f97c6d commit 5d89947
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 1 deletion.
4 changes: 3 additions & 1 deletion CHANGES.md
@@ -1,7 +1,9 @@
2.1.0 (in development)
======================


- Support for pickling `abc.abstractproperty`, `abc.abstractclassmethod`,
and `abc.abstractstaticmethod`.
([PR #450](https://github.com/cloudpipe/cloudpickle/pull/450))

2.0.0
=====
Expand Down
4 changes: 4 additions & 0 deletions cloudpickle/cloudpickle_fast.py
Expand Up @@ -534,6 +534,10 @@ class CloudPickler(Pickler):
_dispatch_table[type(OrderedDict().keys())] = _odict_keys_reduce
_dispatch_table[type(OrderedDict().values())] = _odict_values_reduce
_dispatch_table[type(OrderedDict().items())] = _odict_items_reduce
_dispatch_table[abc.abstractmethod] = _classmethod_reduce
_dispatch_table[abc.abstractclassmethod] = _classmethod_reduce
_dispatch_table[abc.abstractstaticmethod] = _classmethod_reduce
_dispatch_table[abc.abstractproperty] = _property_reduce


dispatch_table = ChainMap(_dispatch_table, copyreg.dispatch_table)
Expand Down
106 changes: 106 additions & 0 deletions tests/cloudpickle_test.py
Expand Up @@ -1184,6 +1184,101 @@ def some_classmethod(cls):
def some_staticmethod():
"""A staticmethod"""

@property
@abc.abstractmethod
def some_property():
"""A property"""

class ConcreteClass(AbstractClass):
def some_method(self):
return 'it works!'

@classmethod
def some_classmethod(cls):
assert cls == ConcreteClass
return 'it works!'

@staticmethod
def some_staticmethod():
return 'it works!'

@property
def some_property(self):
return 'it works!'

# This abstract class is locally defined so we can safely register
# tuple in it to verify the unpickled class also register tuple.
AbstractClass.register(tuple)

concrete_instance = ConcreteClass()
depickled_base = pickle_depickle(AbstractClass, protocol=self.protocol)
depickled_class = pickle_depickle(ConcreteClass,
protocol=self.protocol)
depickled_instance = pickle_depickle(concrete_instance)

assert issubclass(tuple, AbstractClass)
assert issubclass(tuple, depickled_base)

self.assertEqual(depickled_class().some_method(), 'it works!')
self.assertEqual(depickled_instance.some_method(), 'it works!')

self.assertEqual(depickled_class.some_classmethod(), 'it works!')
self.assertEqual(depickled_instance.some_classmethod(), 'it works!')

self.assertEqual(depickled_class().some_staticmethod(), 'it works!')
self.assertEqual(depickled_instance.some_staticmethod(), 'it works!')

self.assertEqual(depickled_class().some_property, 'it works!')
self.assertEqual(depickled_instance.some_property, 'it works!')
self.assertRaises(TypeError, depickled_base)

class DepickledBaseSubclass(depickled_base):
def some_method(self):
return 'it works for realz!'

@classmethod
def some_classmethod(cls):
assert cls == DepickledBaseSubclass
return 'it works for realz!'

@staticmethod
def some_staticmethod():
return 'it works for realz!'

@property
def some_property():
return 'it works for realz!'

self.assertEqual(DepickledBaseSubclass().some_method(),
'it works for realz!')

class IncompleteBaseSubclass(depickled_base):
def some_method(self):
return 'this class lacks some concrete methods'

self.assertRaises(TypeError, IncompleteBaseSubclass)

def test_abstracts(self):
# Same as `test_abc` but using deprecated `abc.abstract*` methods.
# See https://github.com/cloudpipe/cloudpickle/issues/367

class AbstractClass(abc.ABC):
@abc.abstractmethod
def some_method(self):
"""A method"""

@abc.abstractclassmethod
def some_classmethod(cls):
"""A classmethod"""

@abc.abstractstaticmethod
def some_staticmethod():
"""A staticmethod"""

@abc.abstractproperty
def some_property(self):
"""A property"""

class ConcreteClass(AbstractClass):
def some_method(self):
return 'it works!'
Expand All @@ -1197,6 +1292,10 @@ def some_classmethod(cls):
def some_staticmethod():
return 'it works!'

@property
def some_property(self):
return 'it works!'

# This abstract class is locally defined so we can safely register
# tuple in it to verify the unpickled class also register tuple.
AbstractClass.register(tuple)
Expand All @@ -1218,6 +1317,9 @@ def some_staticmethod():

self.assertEqual(depickled_class().some_staticmethod(), 'it works!')
self.assertEqual(depickled_instance.some_staticmethod(), 'it works!')

self.assertEqual(depickled_class().some_property, 'it works!')
self.assertEqual(depickled_instance.some_property, 'it works!')
self.assertRaises(TypeError, depickled_base)

class DepickledBaseSubclass(depickled_base):
Expand All @@ -1233,6 +1335,10 @@ def some_classmethod(cls):
def some_staticmethod():
return 'it works for realz!'

@property
def some_property(self):
return 'it works for realz!'

self.assertEqual(DepickledBaseSubclass().some_method(),
'it works for realz!')

Expand Down

0 comments on commit 5d89947

Please sign in to comment.