]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- An exception is raised in the unusual case that an
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 10 Feb 2011 16:30:23 +0000 (11:30 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 10 Feb 2011 16:30:23 +0000 (11:30 -0500)
append or similar event on a collection occurs after
the parent object has been dereferenced, which
prevents the parent from being marked as "dirty"
in the session.  Will commit as a warning in 0.6.
[ticket:2046]

CHANGES
lib/sqlalchemy/orm/exc.py
lib/sqlalchemy/orm/state.py
test/orm/test_attributes.py

diff --git a/CHANGES b/CHANGES
index f8367673584a2a3ef98ed18355cb719255f10b9b..ad123388bc99eb9718d7ede3ceb166ce018ab96e 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -111,6 +111,13 @@ CHANGES
   - Session weak_instance_dict=False is deprecated.
     [ticket:1473]
 
+  - An exception is raised in the unusual case that an 
+    append or similar event on a collection occurs after
+    the parent object has been dereferenced, which 
+    prevents the parent from being marked as "dirty" 
+    in the session.  Was a warning in 0.6.6.
+    [ticket:2046]
+
 - sql
   - LIMIT/OFFSET clauses now use bind parameters
     [ticket:805]
@@ -230,6 +237,13 @@ CHANGES
    or exclude_properties would result in UnmappedColumnError.
    [ticket:1995]
 
+  - A warning is emitted in the unusual case that an 
+    append or similar event on a collection occurs after
+    the parent object has been dereferenced, which 
+    prevents the parent from being marked as "dirty" 
+    in the session.  This will be an exception in 0.7.
+    [ticket:2046]
+
 - sql
   - Column.copy(), as used in table.tometadata(), copies the 
     'doc' attribute.  [ticket:2028]
index b86e5c7c387336afe4f0ad554ecbae79df742c88..3bfb2708cd637034fc90b456f802fa698f251902 100644 (file)
@@ -40,6 +40,9 @@ class FlushError(sa.exc.SQLAlchemyError):
 class UnmappedError(sa.exc.InvalidRequestError):
     """Base for exceptions that involve expected mappings not present."""
 
+class ObjectDereferencedError(sa.exc.SQLAlchemyError):
+    """An operation cannot complete due to an object being garbage collected."""
+
 class DetachedInstanceError(sa.exc.SQLAlchemyError):
     """An attempt to access unloaded attributes on a 
     mapped instance that is detached."""
index 7a93b5cb0be5bc324e66b9c38d427449dde77633..6b410a868e3e06965b61845b02b9f3f8691eae38 100644 (file)
@@ -15,7 +15,8 @@ from sqlalchemy.util import EMPTY_SET
 import weakref
 from sqlalchemy import util
 
-from sqlalchemy.orm import exc as orm_exc, attributes, interfaces
+from sqlalchemy.orm import exc as orm_exc, attributes, interfaces,\
+        util as orm_util
 from sqlalchemy.orm.attributes import PASSIVE_OFF, PASSIVE_NO_RESULT, \
     PASSIVE_NO_FETCH, NEVER_SET, ATTR_WAS_SET, NO_VALUE
 
@@ -351,7 +352,11 @@ class InstanceState(object):
                 instance_dict._modified.add(self)
 
             self._strong_obj = self.obj()
-
+            if self._strong_obj is None:
+                raise orm_exc.ObjectDereferencedError(
+                        "Can't emit change event for attribute '%s' - parent object "
+                        "of type %s has been garbage collected." 
+                        % (self.manager[attr.key], orm_util.state_class_str(self)))
             self.modified = True
 
     def commit(self, dict_, keys):
index 5d74c2062791cd79d71fec23b3e8e9c78dae6b04..46bddb04713545880acc83e4c858d9cf0d098530 100644 (file)
@@ -1,10 +1,10 @@
 import pickle
-from sqlalchemy.orm import attributes, instrumentation
+from sqlalchemy.orm import attributes, instrumentation, exc as orm_exc
 from sqlalchemy.orm.collections import collection
 from sqlalchemy.orm.interfaces import AttributeExtension
 from sqlalchemy import exc as sa_exc
 from test.lib import *
-from test.lib.testing import eq_, ne_, assert_raises
+from test.lib.testing import eq_, ne_, assert_raises, assert_raises_message
 from test.orm import _base
 from test.lib.util import gc_collect, all_partial_orderings
 from sqlalchemy.util import cmp, jython, topological
@@ -139,6 +139,28 @@ class AttributesTest(_base.ORMTest):
         assert state.obj() is None
         assert state.dict == {}
 
+    def test_object_dereferenced_error(self):
+        class Foo(object):
+            pass
+        class Bar(object):
+            def __init__(self):
+                gc_collect()
+
+        instrumentation.register_class(Foo)
+        instrumentation.register_class(Bar)
+        attributes.register_attribute(Foo, 
+                                    'bars', 
+                                    uselist=True, 
+                                    useobject=True)
+
+        assert_raises_message(
+            orm_exc.ObjectDereferencedError,
+            "Can't emit change event for attribute "
+            "'Foo.bars' - parent object of type <Foo> "
+            "has been garbage collected.",
+            lambda: Foo().bars.append(Bar())
+        )
+
     def test_deferred(self):
         class Foo(object):pass