Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cloudpickle/cloudpickle.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,14 @@ def _class_getstate(obj):
clsdict = _extract_class_dict(obj)
clsdict.pop("__weakref__", None)

# In Python 3.13+, classes with annotations have an __annotate__ function
# that may have closures referencing unpicklable objects in the class
# namespace. We don't need to pickle it as Python will regenerate it from
# __annotations__ when the class is reconstructed.
clsdict.pop("__annotate__", None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably useless.

# Also handle __annotate_func__ which may appear in some Python 3.14 builds
clsdict.pop("__annotate_func__", None)

if issubclass(type(obj), abc.ABCMeta):
# If obj is an instance of an ABCMeta subclass, don't pickle the
# cache/negative caches populated during isinstance/issubclass
Expand Down
27 changes: 27 additions & 0 deletions tests/cloudpickle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1429,6 +1429,33 @@ def some_method(self):

self.assertRaises(TypeError, IncompleteBaseSubclass)

def test_abc_with_annotations(self):
# Test pickling abstract classes with type annotations.
# This is a regression test for Python 3.13+ (PEP 649) where __annotate__
# functions can have closures that reference _abc_impl.

def class_factory():
class Model(abc.ABC):
field1: int
field2: str
field3: float

return Model

Model = class_factory()

# This should not raise TypeError about unpicklable _abc._abc_data
depickled = pickle_depickle(Model, protocol=self.protocol)

# Verify the annotations are preserved
self.assertEqual(
depickled.__annotations__,
{"field1": int, "field2": str, "field3": float},
)

# Verify it's still an ABC
self.assertTrue(issubclass(depickled, abc.ABC))

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
Expand Down
Loading