From: Mike Bayer Date: Thu, 13 May 2010 19:36:22 +0000 (-0400) Subject: - fixed __setstate__ method of CollectionAdapter to not X-Git-Tag: rel_0_6_1~18 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e352e255a749b6e75e18bd41bf6646eeea345442;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - fixed __setstate__ method of CollectionAdapter to not fail during deserialize where parent InstanceState not yet unserialized. [ticket:1802] --- diff --git a/CHANGES b/CHANGES index b49116a614..5762bd68ec 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,10 @@ CHANGES - session.merge() will not expire attributes on the returned instance if that instance is "pending". [ticket:1789] + - fixed __setstate__ method of CollectionAdapter to not + fail during deserialize where parent InstanceState not + yet unserialized. [ticket:1802] + - sql - expr.in_() now accepts a text() construct as the argument. Grouping parenthesis are added automatically, i.e. usage diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py index 58b383a291..0ea17cd8bd 100644 --- a/lib/sqlalchemy/orm/collections.py +++ b/lib/sqlalchemy/orm/collections.py @@ -471,15 +471,20 @@ class CollectionAdapter(object): """ def __init__(self, attr, owner_state, data): - self.attr = attr - # TODO: figure out what this being a weakref buys us + self._key = attr.key self._data = weakref.ref(data) self.owner_state = owner_state self.link_to_self(data) - - data = property(lambda s: s._data(), - doc="The entity collection being adapted.") - + + @property + def data(self): + "The entity collection being adapted." + return self._data() + + @util.memoized_property + def attr(self): + return self.owner_state.manager[self._key].impl + def link_to_self(self, data): """Link a collection to this adapter, and fire a link event.""" setattr(data, '_sa_adapter', self) @@ -619,12 +624,12 @@ class CollectionAdapter(object): initiator=initiator) def __getstate__(self): - return {'key': self.attr.key, + return {'key': self._key, 'owner_state': self.owner_state, 'data': self.data} def __setstate__(self, d): - self.attr = getattr(d['owner_state'].obj().__class__, d['key']).impl + self._key = d['key'] self.owner_state = d['owner_state'] self._data = weakref.ref(d['data']) diff --git a/test/orm/test_pickled.py b/test/orm/test_pickled.py index 4cdfa4181c..5b87b2b85a 100644 --- a/test/orm/test_pickled.py +++ b/test/orm/test_pickled.py @@ -3,7 +3,7 @@ import pickle import sqlalchemy as sa from sqlalchemy.test import testing from sqlalchemy.test.testing import assert_raises_message -from sqlalchemy import Integer, String, ForeignKey, exc +from sqlalchemy import Integer, String, ForeignKey, exc, MetaData from sqlalchemy.test.schema import Table, Column from sqlalchemy.orm import mapper, relationship, create_session, \ sessionmaker, attributes, interfaces,\ @@ -218,6 +218,49 @@ class PickleTest(_fixtures.FixtureTest): u2 = pickle.loads(pickle.dumps(u1)) + def test_collection_setstate(self): + """test a particular cycle that requires CollectionAdapter + to not rely upon InstanceState to deserialize.""" + + global Child1, Child2, Parent, Screen + + m = MetaData() + c1 = Table('c1', m, + Column('parent_id', String, + ForeignKey('p.id'), primary_key=True) + ) + c2 = Table('c2', m, + Column('parent_id', String, + ForeignKey('p.id'), primary_key=True) + ) + p = Table('p', m, + Column('id', String, primary_key=True) + ) + class Child1(_base.ComparableEntity): + pass + + class Child2(_base.ComparableEntity): + pass + + class Parent(_base.ComparableEntity): + pass + + mapper(Parent, p, properties={ + 'children1':relationship(Child1), + 'children2':relationship(Child2) + }) + mapper(Child1, c1) + mapper(Child2, c2) + class Screen(object): + def __init__(self, obj, parent=None): + self.obj = obj + self.parent = parent + + obj = Parent() + screen1 = Screen(obj) + screen1.errors = [obj.children1, obj.children2] + screen2 = Screen(Child2(), screen1) + pickle.loads(pickle.dumps(screen2)) class PolymorphicDeferredTest(_base.MappedTest): @classmethod