From 6bd22e2eca7fce8b7eb8a050f07a49fa160d81ad Mon Sep 17 00:00:00 2001 From: James Bourbeau Date: Fri, 24 Sep 2021 19:19:45 -0500 Subject: [PATCH 1/3] Add support for abc.abstract* methods --- cloudpickle/cloudpickle_fast.py | 4 ++ tests/cloudpickle_test.py | 90 +++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) diff --git a/cloudpickle/cloudpickle_fast.py b/cloudpickle/cloudpickle_fast.py index ac4d08bb..70b5ecec 100644 --- a/cloudpickle/cloudpickle_fast.py +++ b/cloudpickle/cloudpickle_fast.py @@ -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) diff --git a/tests/cloudpickle_test.py b/tests/cloudpickle_test.py index 22e31d8d..aec67a71 100644 --- a/tests/cloudpickle_test.py +++ b/tests/cloudpickle_test.py @@ -1242,6 +1242,96 @@ def some_method(self): 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 staticmethod""" + + 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(self): + 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_weakset_identity_preservation(self): # Test that weaksets don't lose all their inhabitants if they're # pickled in a larger data structure that includes other references to From feb76b59f21f04d621bf316ca698f5ef47507d66 Mon Sep 17 00:00:00 2001 From: James Bourbeau Date: Fri, 24 Sep 2021 19:20:10 -0500 Subject: [PATCH 2/3] Extend test_abc to include abstract properties --- tests/cloudpickle_test.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/cloudpickle_test.py b/tests/cloudpickle_test.py index aec67a71..f356681d 100644 --- a/tests/cloudpickle_test.py +++ b/tests/cloudpickle_test.py @@ -1184,6 +1184,11 @@ 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!' @@ -1197,6 +1202,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) @@ -1218,6 +1227,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): @@ -1233,6 +1245,10 @@ def some_classmethod(cls): 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!') @@ -1261,7 +1277,7 @@ def some_staticmethod(): @abc.abstractproperty def some_property(self): - """A staticmethod""" + """A property""" class ConcreteClass(AbstractClass): def some_method(self): From 2f97c6d224c2bc360ed9c4599d8c08aba9b06ffe Mon Sep 17 00:00:00 2001 From: James Bourbeau Date: Tue, 12 Oct 2021 15:20:53 -0500 Subject: [PATCH 3/3] Add changelog entry --- CHANGES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 89089865..972cf3f0 100644 --- a/CHANGES.md +++ b/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 =====