From: Mike Bayer Date: Thu, 10 Feb 2011 16:42:35 +0000 (-0500) Subject: - A warning is emitted in the unusual case that an X-Git-Tag: rel_0_6_7~35 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d8aa3383d5c88e2b503087d6dd0480146fe8bdf5;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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] --- diff --git a/CHANGES b/CHANGES index 380a4881ff..67b87b801f 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,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/state.py b/lib/sqlalchemy/orm/state.py index 0470a382fd..846ad1328e 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -8,9 +8,9 @@ from sqlalchemy.util import EMPTY_SET import weakref from sqlalchemy import util from sqlalchemy.orm.attributes import PASSIVE_NO_RESULT, PASSIVE_OFF, \ - NEVER_SET, NO_VALUE, manager_of_class, \ - ATTR_WAS_SET -from sqlalchemy.orm import attributes, exc as orm_exc, interfaces + NEVER_SET, NO_VALUE, manager_of_class, ATTR_WAS_SET +from sqlalchemy.orm import attributes, exc as orm_exc, interfaces, \ + util as orm_util import sys attributes.state = sys.modules['sqlalchemy.orm.state'] @@ -357,7 +357,16 @@ class InstanceState(object): instance_dict._modified.add(self) self._strong_obj = self.obj() - + if self._strong_obj is None: + util.warn( + "Can't emit change event for attribute '%s.%s' " + "- parent object of type %s has been garbage " + "collected." + % ( + self.class_.__name__, + attr.key, + orm_util.state_class_str(self) + )) self.modified = True def commit(self, dict_, keys): diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index f26da6c7d1..826e9f3524 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -669,6 +669,14 @@ def state_str(state): else: return '<%s at 0x%x>' % (state.class_.__name__, id(state.obj())) +def state_class_str(state): + """Return a string describing an instance's class via its InstanceState.""" + + if state is None: + return "None" + else: + return '<%s>' % (state.class_.__name__, ) + def attribute_str(instance, attribute): return instance_str(instance) + "." + attribute diff --git a/test/orm/test_attributes.py b/test/orm/test_attributes.py index 2596268a3f..217066b071 100644 --- a/test/orm/test_attributes.py +++ b/test/orm/test_attributes.py @@ -4,7 +4,7 @@ from sqlalchemy.orm.collections import collection from sqlalchemy.orm.interfaces import AttributeExtension from sqlalchemy import exc as sa_exc from sqlalchemy.test import * -from sqlalchemy.test.testing import eq_, ne_, assert_raises +from sqlalchemy.test.testing import eq_, ne_, assert_raises, assert_raises_message from test.orm import _base from sqlalchemy.test.util import gc_collect from sqlalchemy.util import cmp, jython @@ -126,6 +126,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() + + attributes.register_class(Foo) + attributes.register_class(Bar) + attributes.register_attribute(Foo, + 'bars', + uselist=True, + useobject=True) + + assert_raises_message( + sa_exc.SAWarning, + "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