]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- fixed __setstate__ method of CollectionAdapter to not
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 13 May 2010 19:36:22 +0000 (15:36 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 13 May 2010 19:36:22 +0000 (15:36 -0400)
fail during deserialize where parent InstanceState not
yet unserialized.  [ticket:1802]

CHANGES
lib/sqlalchemy/orm/collections.py
test/orm/test_pickled.py

diff --git a/CHANGES b/CHANGES
index b49116a614b5f328ba6f1f4bef6dcb9914d8224b..5762bd68ec2c87b5fea05ac2c91b9ec12a55dfe4 100644 (file)
--- 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
index 58b383a291b72cec8f118ec5daeb057666f4668a..0ea17cd8bd72b448573d42d90304651c63e0f387 100644 (file)
@@ -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'])
 
index 4cdfa4181c4796f7a060943f51713a1fff837d94..5b87b2b85aaf8fd08b64a35131344805e96bb282 100644 (file)
@@ -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