From: Mike Bayer Date: Mon, 14 Nov 2016 21:09:13 +0000 (-0500) Subject: Add missing items to collection.__getstate__ X-Git-Tag: rel_1_1_4~2^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3e063525f3721ef6fa2c3dd3e6ded67c2fa9cb0b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Add missing items to collection.__getstate__ the refactor in b606e47ddc54 / ticket:3457 failed to adjust __getstate__ / __setstate__. need to memoize a few more things including the class itself so that we can navigate back to "attr". Change-Id: I4ece2a616cb8b9dac7b50763ca59e47d0f26cfdf Fixes: #3852 --- diff --git a/doc/build/changelog/changelog_11.rst b/doc/build/changelog/changelog_11.rst index 1a30e2a4ac..dab96fb71c 100644 --- a/doc/build/changelog/changelog_11.rst +++ b/doc/build/changelog/changelog_11.rst @@ -29,6 +29,15 @@ autoincrement setting (see :ref:`change_3216`) would fail to emit correctly when invoked upon a lower-case :func:`.table` construct. + .. change:: 3852 + :tags: bug, orm + :tickets: 3852 + + Fixed regression in collections due to :ticket:`3457` whereby + deserialize during pickle or deepcopy would fail to establish all + attributes of an ORM collection, causing further mutation operations to + fail. + .. change:: default_schema :tags: bug, engine diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py index 1e022e1dd8..47cd774d6f 100644 --- a/lib/sqlalchemy/orm/collections.py +++ b/lib/sqlalchemy/orm/collections.py @@ -714,12 +714,18 @@ class CollectionAdapter(object): def __getstate__(self): return {'key': self._key, 'owner_state': self.owner_state, - 'data': self.data} + 'owner_cls': self.owner_state.class_, + 'data': self.data, + 'invalidated': self.invalidated} def __setstate__(self, d): self._key = d['key'] self.owner_state = d['owner_state'] self._data = weakref.ref(d['data']) + self._converter = d['data']._sa_converter + d['data']._sa_adapter = self + self.invalidated = d['invalidated'] + self.attr = getattr(d['owner_cls'], self._key).impl def bulk_replace(values, existing_adapter, new_adapter): diff --git a/test/orm/test_pickled.py b/test/orm/test_pickled.py index db2a27c77a..f4e49c518a 100644 --- a/test/orm/test_pickled.py +++ b/test/orm/test_pickled.py @@ -1,6 +1,7 @@ from sqlalchemy.testing import eq_ from sqlalchemy.util import pickle import sqlalchemy as sa +import copy from sqlalchemy import testing from sqlalchemy.testing.util import picklers from sqlalchemy.testing import assert_raises_message @@ -173,6 +174,36 @@ class PickleTest(fixtures.MappedTest): sess.add(u2) assert u2.addresses + def test_invalidated_flag_pickle(self): + users, addresses = (self.tables.users, + self.tables.addresses) + + mapper(User, users, properties={ + 'addresses': relationship(Address, lazy='noload') + }) + mapper(Address, addresses) + + u1 = User() + u1.addresses.append(Address()) + u2 = pickle.loads(pickle.dumps(u1)) + u2.addresses.append(Address()) + eq_(len(u2.addresses), 2) + + def test_invalidated_flag_deepcopy(self): + users, addresses = (self.tables.users, + self.tables.addresses) + + mapper(User, users, properties={ + 'addresses': relationship(Address, lazy='noload') + }) + mapper(Address, addresses) + + u1 = User() + u1.addresses.append(Address()) + u2 = copy.deepcopy(u1) + u2.addresses.append(Address()) + eq_(len(u2.addresses), 2) + @testing.requires.non_broken_pickle def test_instance_deferred_cols(self): users, addresses = (self.tables.users,