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

Add support for abc.abstract* methods #450

Merged
merged 3 commits into from Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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