From: Mike Bayer Date: Thu, 10 Feb 2011 16:30:23 +0000 (-0500) Subject: - An exception is raised in the unusual case that an X-Git-Tag: rel_0_7b1~21 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e155b7b8640c18e5ca33d5529fbc11f4776ae4c5;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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. Will commit as a warning in 0.6. [ticket:2046] --- diff --git a/CHANGES b/CHANGES index f836767358..ad123388bc 100644 --- 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] diff --git a/lib/sqlalchemy/orm/exc.py b/lib/sqlalchemy/orm/exc.py index b86e5c7c38..3bfb2708cd 100644 --- a/lib/sqlalchemy/orm/exc.py +++ b/lib/sqlalchemy/orm/exc.py @@ -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.""" diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 7a93b5cb0b..6b410a868e 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -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): diff --git a/test/orm/test_attributes.py b/test/orm/test_attributes.py index 5d74c20627..46bddb0471 100644 --- a/test/orm/test_attributes.py +++ b/test/orm/test_attributes.py @@ -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 " + "has been garbage collected.", + lambda: Foo().bars.append(Bar()) + ) + def test_deferred(self): class Foo(object):pass