diff --git a/changelog.d/1085.change.md b/changelog.d/1085.change.md new file mode 100644 index 000000000..f81545775 --- /dev/null +++ b/changelog.d/1085.change.md @@ -0,0 +1 @@ +Restored ability to unpickle instances pickled before to v22.2.0. diff --git a/src/attr/_make.py b/src/attr/_make.py index 1bb0f5089..014b7bc23 100644 --- a/src/attr/_make.py +++ b/src/attr/_make.py @@ -934,9 +934,15 @@ def slots_setstate(self, state): Automatically created by attrs. """ __bound_setattr = _obj_setattr.__get__(self) - for name in state_attr_names: - if name in state: - __bound_setattr(name, state[name]) + if isinstance(state, tuple): + # Backward compatibility with attrs instances pickled with + # attrs versions before v22.2.0 which stored tuples. + for name, value in zip(state_attr_names, state): + __bound_setattr(name, value) + else: + for name in state_attr_names: + if name in state: + __bound_setattr(name, state[name]) # The hash code cache is not included when the object is # serialized, but it still needs to be initialized to None to diff --git a/tests/test_slots.py b/tests/test_slots.py index f436b3b8a..224fd052a 100644 --- a/tests/test_slots.py +++ b/tests/test_slots.py @@ -803,3 +803,21 @@ class NEW_A: assert new_a.b == 2 assert new_a.c == 3 assert not hasattr(new_a, "d") + + +@pytest.mark.parametrize("cls", [A]) +def test_slots_unpickle_is_backward_compatible(cls, frozen): + """ + Ensure object pickled before v22.2.0 can still be unpickled. + """ + a = cls(1, 2, 3) + + a_pickled = ( + b"\x80\x04\x95&\x00\x00\x00\x00\x00\x00\x00\x8c\x10" + + a.__module__.encode() + + b"\x94\x8c\x01A\x94\x93\x94)\x81\x94K\x01K\x02K\x03\x87\x94b." + ) + + a_unpickled = pickle.loads(a_pickled) + + assert a_unpickled == a