From: Mike Bayer Date: Sun, 31 Jul 2011 21:32:07 +0000 (-0400) Subject: - Load of a deferred() attribute on an object X-Git-Tag: rel_0_7_2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1a0c8ace3010d9d7bcfd651d4bb1340b83b3850b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Load of a deferred() attribute on an object where row can't be located raises ObjectDeletedError instead of failing later on; improved the message in ObjectDeletedError to include other conditions besides a simple "delete". [ticket:2191] - break up test_get_refreshes() in test_expire --- diff --git a/CHANGES b/CHANGES index 1dcebbb879..ec66db0c15 100644 --- a/CHANGES +++ b/CHANGES @@ -55,6 +55,13 @@ CHANGES warning, which is being considered for 0.8. [ticket:2205] + - Load of a deferred() attribute on an object + where row can't be located raises + ObjectDeletedError instead of failing later + on; improved the message in ObjectDeletedError + to include other conditions besides a simple + "delete". [ticket:2191] + - Fixed regression from 0.6 where a get history operation on some relationship() based attributes would fail when a lazyload would emit; this could diff --git a/lib/sqlalchemy/orm/exc.py b/lib/sqlalchemy/orm/exc.py index 1812f0d720..98b97059ea 100644 --- a/lib/sqlalchemy/orm/exc.py +++ b/lib/sqlalchemy/orm/exc.py @@ -7,7 +7,7 @@ """SQLAlchemy ORM exceptions.""" import sqlalchemy as sa - +orm_util = sa.util.importlater('sqlalchemy.orm', 'util') NO_STATE = (AttributeError, KeyError) """Exception types that may be raised by instrumentation implementations.""" @@ -95,7 +95,12 @@ class ObjectDeletedError(sa.exc.InvalidRequestError): object. """ - + def __init__(self, state): + sa.exc.InvalidRequestError.__init__( + self, + "Instance '%s' has been deleted, or its " + "row is otherwise not present." % orm_util.state_str(state) + ) class UnmappedColumnError(sa.exc.InvalidRequestError): """Mapping operation was requested on an unknown column.""" diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 3c3389d797..becc470521 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -1586,9 +1586,7 @@ class Mapper(object): # if instance is pending, a refresh operation # may not complete (even if PK attributes are assigned) if has_key and result is None: - raise orm_exc.ObjectDeletedError( - "Instance '%s' has been deleted." % - state_str(state)) + raise orm_exc.ObjectDeletedError(state) def _optimized_get_statement(self, state, attribute_names): """assemble a WHERE clause which retrieves a given state by primary diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index aacf466d62..c335d1509a 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -242,8 +242,10 @@ class DeferredColumnLoader(LoaderStrategy): ) query = session.query(localparent) - query._load_on_ident(state.key, - only_load_props=group, refresh_state=state) + if query._load_on_ident(state.key, + only_load_props=group, refresh_state=state) is None: + raise orm_exc.ObjectDeletedError(state) + return attributes.ATTR_WAS_SET log.class_logger(DeferredColumnLoader) diff --git a/test/orm/test_expire.py b/test/orm/test_expire.py index 886af0d265..57bd2c8cc8 100644 --- a/test/orm/test_expire.py +++ b/test/orm/test_expire.py @@ -88,6 +88,13 @@ class ExpireTest(_fixtures.FixtureTest): u = s.query(User).get(10) # expire flag reset, so not expired self.assert_sql_count(testing.db, go, 0) + def test_get_on_deleted_expunges(self): + users, User = self.tables.users, self.classes.User + + mapper(User, users) + s = create_session(autocommit=False) + u = s.query(User).get(10) + s.expire_all() s.execute(users.delete().where(User.id==10)) @@ -96,33 +103,54 @@ class ExpireTest(_fixtures.FixtureTest): assert s.query(User).get(10) is None assert u not in s # and expunges - # trick the "deleted" flag so we can re-add for the sake - # of this test - del attributes.instance_state(u).deleted + def test_refresh_on_deleted_raises(self): + users, User = self.tables.users, self.classes.User - # add it back - s.add(u) - # nope, raises ObjectDeletedError - assert_raises(sa.orm.exc.ObjectDeletedError, getattr, u, 'name') + mapper(User, users) + s = create_session(autocommit=False) + u = s.query(User).get(10) + s.expire_all() + + s.expire_all() + s.execute(users.delete().where(User.id==10)) + + # raises ObjectDeletedError + assert_raises_message( + sa.orm.exc.ObjectDeletedError, + "Instance '' has been " + "deleted, or its row is otherwise not present.", + getattr, u, 'name' + ) + + def test_rollback_undoes_expunge_from_deleted(self): + users, User = self.tables.users, self.classes.User - # do a get()/remove u from session again + mapper(User, users) + s = create_session(autocommit=False) + u = s.query(User).get(10) + s.expire_all() + s.execute(users.delete().where(User.id==10)) + + # do a get()/remove u from session assert s.query(User).get(10) is None assert u not in s s.rollback() assert u in s - # but now its back, rollback has occurred, the _remove_newly_deleted - # is reverted + # but now its back, rollback has occurred, the + # _remove_newly_deleted is reverted eq_(u.name, 'chuck') def test_deferred(self): - """test that unloaded, deferred attributes aren't included in the expiry list.""" + """test that unloaded, deferred attributes aren't included in the + expiry list.""" Order, orders = self.classes.Order, self.tables.orders - mapper(Order, orders, properties={'description':deferred(orders.c.description)}) + mapper(Order, orders, properties={ + 'description':deferred(orders.c.description)}) s = create_session() o1 = s.query(Order).first() @@ -132,6 +160,23 @@ class ExpireTest(_fixtures.FixtureTest): assert 'description' not in o1.__dict__ assert o1.description + def test_deferred_notfound(self): + Order, orders = self.classes.Order, self.tables.orders + + mapper(Order, orders, properties={ + 'description':deferred(orders.c.description)}) + s = create_session() + o1 = s.query(Order).first() + assert 'description' not in o1.__dict__ + s.expire(o1) + s.query(Order).delete() + assert_raises_message( + sa.orm.exc.ObjectDeletedError, + "Instance '' has been " + "deleted, or its row is otherwise not present.", + getattr, o1, 'description' + ) + def test_lazyload_autoflushes(self): users, Address, addresses, User = (self.tables.users, self.classes.Address, @@ -139,7 +184,8 @@ class ExpireTest(_fixtures.FixtureTest): self.classes.User) mapper(User, users, properties={ - 'addresses':relationship(Address, order_by=addresses.c.email_address) + 'addresses':relationship(Address, + order_by=addresses.c.email_address) }) mapper(Address, addresses) s = create_session(autoflush=True, autocommit=False)