From 03167fb4bbb2d4a1d68ef04dff02ed2033b4f138 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sun, 7 Feb 2010 23:58:17 +0000 Subject: [PATCH] - Now uses sqlalchemy.orm.exc.DetachedInstanceError when an attribute load or refresh action fails due to object being detached from any Session. UnboundExecutionError is specific to engines bound to sessions and statements. --- CHANGES | 5 +++++ lib/sqlalchemy/orm/dynamic.py | 5 +++-- lib/sqlalchemy/orm/exc.py | 4 +++- lib/sqlalchemy/orm/mapper.py | 18 +++++++++--------- lib/sqlalchemy/orm/strategies.py | 8 ++++---- test/orm/test_expire.py | 10 +++++----- test/orm/test_lazy_relations.py | 4 ++-- 7 files changed, 31 insertions(+), 23 deletions(-) diff --git a/CHANGES b/CHANGES index d88a6bfdc8..5ecb3c4dd5 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,11 @@ CHANGES needless updates of the primary key column during a so-called "row switch" operation, i.e. add + delete of two objects with the same PK. + + - Now uses sqlalchemy.orm.exc.DetachedInstanceError when an + attribute load or refresh action fails due to object + being detached from any Session. UnboundExecutionError + is specific to engines bound to sessions and statements. - sql - Added math negation operator support, -x. diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 456bcd34e6..308d69fe8a 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -12,7 +12,8 @@ basic add/delete mutation. """ from sqlalchemy import log, util -import sqlalchemy.exceptions as sa_exc +from sqlalchemy import exc as sa_exc +from sqlalchemy.orm import exc as sa_exc from sqlalchemy.sql import operators from sqlalchemy.orm import ( attributes, object_session, util as mapperutil, strategies, object_mapper @@ -234,7 +235,7 @@ class AppenderMixin(object): if sess is None: sess = object_session(instance) if sess is None: - raise sa_exc.UnboundExecutionError( + raise orm_exc.DetachedInstanceError( "Parent instance %s is not bound to a Session, and no " "contextual session is established; lazy load operation " "of attribute '%s' cannot proceed" % ( diff --git a/lib/sqlalchemy/orm/exc.py b/lib/sqlalchemy/orm/exc.py index 8b52eec8ac..431acc15cc 100644 --- a/lib/sqlalchemy/orm/exc.py +++ b/lib/sqlalchemy/orm/exc.py @@ -23,7 +23,9 @@ class FlushError(sa.exc.SQLAlchemyError): class UnmappedError(sa.exc.InvalidRequestError): """TODO""" - +class DetachedInstanceError(sa.exc.SQLAlchemyError): + """An attempt to access unloaded attributes on a mapped instance that is detached.""" + class UnmappedInstanceError(UnmappedError): """An mapping operation was requested for an unknown instance.""" diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 2bdf2b8476..63ad20bcac 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -22,7 +22,7 @@ deque = __import__('collections').deque from sqlalchemy import sql, util, log, exc as sa_exc from sqlalchemy.sql import expression, visitors, operators, util as sqlutil -from sqlalchemy.orm import attributes, exc, sync +from sqlalchemy.orm import attributes, sync, exc as orm_exc from sqlalchemy.orm.interfaces import ( MapperProperty, EXT_CONTINUE, PropComparator ) @@ -1123,9 +1123,9 @@ class Mapper(object): except KeyError: prop = self._props.get(column.key, None) if prop: - raise exc.UnmappedColumnError("Column '%s.%s' is not available, due to conflicting property '%s':%s" % (column.table.name, column.name, column.key, repr(prop))) + raise orm_exc.UnmappedColumnError("Column '%s.%s' is not available, due to conflicting property '%s':%s" % (column.table.name, column.name, column.key, repr(prop))) else: - raise exc.UnmappedColumnError("No column %s is configured on mapper %s..." % (column, self)) + raise orm_exc.UnmappedColumnError("No column %s is configured on mapper %s..." % (column, self)) # TODO: improve names? def _get_state_attr_by_column(self, state, column): @@ -1319,7 +1319,7 @@ class Mapper(object): instance = uowtransaction.session.identity_map[instance_key] existing = attributes.instance_state(instance) if not uowtransaction.is_deleted(existing): - raise exc.FlushError( + raise orm_exc.FlushError( "New instance %s with identity key %s conflicts with persistent instance %s" % (state_str(state), instance_key, state_str(existing))) if self._should_log_debug: @@ -1460,7 +1460,7 @@ class Mapper(object): if connection.dialect.supports_sane_rowcount: if rows != len(update): - raise exc.ConcurrentModificationError( + raise orm_exc.ConcurrentModificationError( "Updated rowcount %d does not match number of objects updated %d" % (rows, len(update))) @@ -1600,7 +1600,7 @@ class Mapper(object): statement = table.delete(clause) c = connection.execute(statement, del_objects) if c.supports_sane_multi_rowcount() and c.rowcount != len(del_objects): - raise exc.ConcurrentModificationError("Deleted rowcount %d does not match " + raise orm_exc.ConcurrentModificationError("Deleted rowcount %d does not match " "number of objects deleted %d" % (c.rowcount, len(del_objects))) for state, mapper, connection in tups: @@ -1725,7 +1725,7 @@ class Mapper(object): if not currentload and version_id_col is not None and context.version_check and \ self._get_state_attr_by_column(state, self.version_id_col) != row[version_id_col]: - raise exc.ConcurrentModificationError( + raise orm_exc.ConcurrentModificationError( "Instance '%s' version of %s does not match %s" % (state_str(state), self._get_state_attr_by_column(state, self.version_id_col), row[version_id_col])) elif refresh_state: @@ -1928,7 +1928,7 @@ def _load_scalar_attributes(state, attribute_names): mapper = _state_mapper(state) session = _state_session(state) if not session: - raise sa_exc.UnboundExecutionError("Instance %s is not bound to a Session; " + raise orm_exc.DetachedInstanceError("Instance %s is not bound to a Session; " "attribute refresh operation cannot proceed" % (state_str(state))) has_key = _state_has_identity(state) @@ -1948,4 +1948,4 @@ def _load_scalar_attributes(state, attribute_names): # if instance is pending, a refresh operation may not complete (even if PK attributes are assigned) if has_key and result is None: - raise exc.ObjectDeletedError("Instance '%s' has been deleted." % state_str(state)) + raise orm_exc.ObjectDeletedError("Instance '%s' has been deleted." % state_str(state)) diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 0970f38362..33e60491a1 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -6,11 +6,11 @@ """sqlalchemy.orm.interfaces.LoaderStrategy implementations, and related MapperOptions.""" -import sqlalchemy.exceptions as sa_exc +from sqlalchemy import exc as sa_exc from sqlalchemy import sql, util, log from sqlalchemy.sql import util as sql_util from sqlalchemy.sql import visitors, expression, operators -from sqlalchemy.orm import mapper, attributes, interfaces +from sqlalchemy.orm import mapper, attributes, interfaces, exc as orm_exc from sqlalchemy.orm.interfaces import ( LoaderStrategy, StrategizedOption, MapperOption, PropertyOption, serialize_path, deserialize_path, StrategizedProperty @@ -291,7 +291,7 @@ class LoadDeferredColumns(object): session = sessionlib._state_session(state) if session is None: - raise sa_exc.UnboundExecutionError( + raise orm_exc.DetachedInstanceError( "Parent instance %s is not bound to a Session; " "deferred load operation of attribute '%s' cannot proceed" % (mapperutil.state_str(state), self.key) @@ -556,7 +556,7 @@ class LoadLazyAttribute(object): session = sessionlib._state_session(state) if session is None: - raise sa_exc.UnboundExecutionError( + raise orm_exc.DetachedInstanceError( "Parent instance %s is not bound to a Session; " "lazy load operation of attribute '%s' cannot proceed" % (mapperutil.state_str(state), self.key) diff --git a/test/orm/test_expire.py b/test/orm/test_expire.py index c602ac963f..c8ce5c7dfe 100644 --- a/test/orm/test_expire.py +++ b/test/orm/test_expire.py @@ -7,7 +7,7 @@ from sqlalchemy.test import testing from sqlalchemy import Integer, String, ForeignKey, exc as sa_exc from sqlalchemy.test.schema import Table from sqlalchemy.test.schema import Column -from sqlalchemy.orm import mapper, relation, create_session, attributes, deferred +from sqlalchemy.orm import mapper, relation, create_session, attributes, deferred, exc as orm_exc from test.orm import _base, _fixtures @@ -59,7 +59,7 @@ class ExpireTest(_fixtures.FixtureTest): u = s.query(User).get(7) s.expunge_all() - assert_raises_message(sa.exc.InvalidRequestError, r"is not persistent within this Session", s.expire, u) + assert_raises_message(sa_exc.InvalidRequestError, r"is not persistent within this Session", s.expire, u) @testing.resolve_artifact_names def test_get_refreshes(self): @@ -190,7 +190,7 @@ class ExpireTest(_fixtures.FixtureTest): sess.expire(u, attribute_names=['name']) sess.expunge(u) - assert_raises(sa.exc.UnboundExecutionError, getattr, u, 'name') + assert_raises(orm_exc.DetachedInstanceError, getattr, u, 'name') @testing.resolve_artifact_names def test_pending_raises(self): @@ -200,7 +200,7 @@ class ExpireTest(_fixtures.FixtureTest): sess = create_session() u = User(id=15) sess.add(u) - assert_raises(sa.exc.InvalidRequestError, sess.expire, u, ['name']) + assert_raises(sa_exc.InvalidRequestError, sess.expire, u, ['name']) @testing.resolve_artifact_names def test_no_instance_key(self): @@ -843,7 +843,7 @@ class RefreshTest(_fixtures.FixtureTest): s = create_session() u = s.query(User).get(7) s.expunge_all() - assert_raises_message(sa.exc.InvalidRequestError, r"is not persistent within this Session", lambda: s.refresh(u)) + assert_raises_message(sa_exc.InvalidRequestError, r"is not persistent within this Session", lambda: s.refresh(u)) @testing.resolve_artifact_names def test_refresh_expired(self): diff --git a/test/orm/test_lazy_relations.py b/test/orm/test_lazy_relations.py index 1104ef1e0a..ac175c83bc 100644 --- a/test/orm/test_lazy_relations.py +++ b/test/orm/test_lazy_relations.py @@ -3,7 +3,7 @@ from sqlalchemy.test.testing import assert_raises, assert_raises_message import datetime from sqlalchemy import exc as sa_exc -from sqlalchemy.orm import attributes +from sqlalchemy.orm import attributes, exc as orm_exc import sqlalchemy as sa from sqlalchemy.test import testing from sqlalchemy import Integer, String, ForeignKey, SmallInteger @@ -39,7 +39,7 @@ class LazyTest(_fixtures.FixtureTest): q = sess.query(User) u = q.filter(users.c.id == 7).first() sess.expunge(u) - assert_raises(sa_exc.InvalidRequestError, getattr, u, 'addresses') + assert_raises(orm_exc.DetachedInstanceError, getattr, u, 'addresses') @testing.resolve_artifact_names def test_orderby(self): -- 2.47.3