diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-02-29 14:40:45 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2020-03-02 17:24:19 -0500 |
commit | 57dc36a01b2b334a996f73f6a78b3bfbe4d9f2ec (patch) | |
tree | 77cbb0199ca91be3b0816e3a5bd4c217e36a7d1b /lib/sqlalchemy/orm | |
parent | 649de79950dcf952d7a44069faf36925c23c4e63 (diff) | |
download | sqlalchemy-57dc36a01b2b334a996f73f6a78b3bfbe4d9f2ec.tar.gz |
Ensure all nested exception throws have a cause
Applied an explicit "cause" to most if not all internally raised exceptions
that are raised from within an internal exception catch, to avoid
misleading stacktraces that suggest an error within the handling of an
exception. While it would be preferable to suppress the internally caught
exception in the way that the ``__suppress_context__`` attribute would,
there does not as yet seem to be a way to do this without suppressing an
enclosing user constructed context, so for now it exposes the internally
caught exception as the cause so that full information about the context
of the error is maintained.
Fixes: #4849
Change-Id: I55a86b29023675d9e5e49bc7edc5a2dc0bcd4751
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 78 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/base.py | 9 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 15 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/loading.py | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 22 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/persistence.py | 31 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 30 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/relationships.py | 88 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/session.py | 68 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/strategy_options.py | 11 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/sync.py | 63 |
11 files changed, 254 insertions, 167 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 66a18da99..a959b0a40 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -231,16 +231,19 @@ class QueryableAttribute( def __getattr__(self, key): try: return getattr(self.comparator, key) - except AttributeError: - raise AttributeError( - "Neither %r object nor %r object associated with %s " - "has an attribute %r" - % ( - type(self).__name__, - type(self.comparator).__name__, - self, - key, - ) + except AttributeError as err: + util.raise_( + AttributeError( + "Neither %r object nor %r object associated with %s " + "has an attribute %r" + % ( + type(self).__name__, + type(self.comparator).__name__, + self, + key, + ) + ), + replace_context=err, ) def __str__(self): @@ -373,31 +376,39 @@ def create_proxied_attribute(descriptor): comparator.""" try: return getattr(descriptor, attribute) - except AttributeError: + except AttributeError as err: if attribute == "comparator": - raise AttributeError("comparator") + util.raise_( + AttributeError("comparator"), replace_context=err + ) try: # comparator itself might be unreachable comparator = self.comparator - except AttributeError: - raise AttributeError( - "Neither %r object nor unconfigured comparator " - "object associated with %s has an attribute %r" - % (type(descriptor).__name__, self, attribute) + except AttributeError as err2: + util.raise_( + AttributeError( + "Neither %r object nor unconfigured comparator " + "object associated with %s has an attribute %r" + % (type(descriptor).__name__, self, attribute) + ), + replace_context=err2, ) else: try: return getattr(comparator, attribute) - except AttributeError: - raise AttributeError( - "Neither %r object nor %r object " - "associated with %s has an attribute %r" - % ( - type(descriptor).__name__, - type(comparator).__name__, - self, - attribute, - ) + except AttributeError as err3: + util.raise_( + AttributeError( + "Neither %r object nor %r object " + "associated with %s has an attribute %r" + % ( + type(descriptor).__name__, + type(comparator).__name__, + self, + attribute, + ) + ), + replace_context=err3, ) Proxy.__name__ = type(descriptor).__name__ + "Proxy" @@ -713,12 +724,15 @@ class AttributeImpl(object): elif value is ATTR_WAS_SET: try: return dict_[key] - except KeyError: + except KeyError as err: # TODO: no test coverage here. - raise KeyError( - "Deferred loader for attribute " - "%r failed to populate " - "correctly" % key + util.raise_( + KeyError( + "Deferred loader for attribute " + "%r failed to populate " + "correctly" % key + ), + replace_context=err, ) elif value is not ATTR_EMPTY: return self.set_committed_value(state, dict_, value) diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py index 571107a38..a31745aec 100644 --- a/lib/sqlalchemy/orm/base.py +++ b/lib/sqlalchemy/orm/base.py @@ -387,9 +387,12 @@ def _entity_descriptor(entity, key): try: return getattr(entity, key) - except AttributeError: - raise sa_exc.InvalidRequestError( - "Entity '%s' has no property '%s'" % (description, key) + except AttributeError as err: + util.raise_( + sa_exc.InvalidRequestError( + "Entity '%s' has no property '%s'" % (description, key) + ), + replace_context=err, ) diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index f75c7d3ba..57c192a5d 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -557,12 +557,15 @@ class StrategizedProperty(MapperProperty): try: return self._strategies[key] except KeyError: - cls = self._strategy_lookup(self, *key) - # this previously was setting self._strategies[cls], that's - # a bad idea; should use strategy key at all times because every - # strategy has multiple keys at this point - self._strategies[key] = strategy = cls(self, key) - return strategy + pass + + # run outside to prevent transfer of exception context + cls = self._strategy_lookup(self, *key) + # this previously was setting self._strategies[cls], that's + # a bad idea; should use strategy key at all times because every + # strategy has multiple keys at this point + self._strategies[key] = strategy = cls(self, key) + return strategy def setup(self, context, query_entity, path, adapter, **kwargs): loader = self._get_context_loader(context, path) diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py index 193980e6c..d943ebb19 100644 --- a/lib/sqlalchemy/orm/loading.py +++ b/lib/sqlalchemy/orm/loading.py @@ -99,9 +99,9 @@ def instances(query, cursor, context): if not query._yield_per: break - except Exception as err: - cursor.close() - util.raise_from_cause(err) + except Exception: + with util.safe_reraise(): + cursor.close() @util.dependencies("sqlalchemy.orm.query") diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 0d87a9c40..91e3251e2 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -1483,11 +1483,14 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr): # it to mapped ColumnProperty try: self.polymorphic_on = self._props[self.polymorphic_on] - except KeyError: - raise sa_exc.ArgumentError( - "Can't determine polymorphic_on " - "value '%s' - no attribute is " - "mapped to this name." % self.polymorphic_on + except KeyError as err: + util.raise_( + sa_exc.ArgumentError( + "Can't determine polymorphic_on " + "value '%s' - no attribute is " + "mapped to this name." % self.polymorphic_on + ), + replace_context=err, ) if self.polymorphic_on in self._columntoproperty: @@ -1987,9 +1990,12 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr): try: return self._props[key] - except KeyError: - raise sa_exc.InvalidRequestError( - "Mapper '%s' has no property '%s'" % (self, key) + except KeyError as err: + util.raise_( + sa_exc.InvalidRequestError( + "Mapper '%s' has no property '%s'" % (self, key) + ), + replace_context=err, ) def get_property_by_column(self, column): diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index 3b274a389..46c84d4bd 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -1635,9 +1635,12 @@ def _sort_states(mapper, states): persistent, key=mapper._persistent_sortkey_fn ) except TypeError as err: - raise sa_exc.InvalidRequestError( - "Could not sort objects by primary key; primary key " - "values must be sortable in Python (was: %s)" % err + util.raise_( + sa_exc.InvalidRequestError( + "Could not sort objects by primary key; primary key " + "values must be sortable in Python (was: %s)" % err + ), + replace_context=err, ) return ( sorted(pending, key=operator.attrgetter("insert_order")) @@ -1681,10 +1684,13 @@ class BulkUD(object): def _factory(cls, lookup, synchronize_session, *arg): try: klass = lookup[synchronize_session] - except KeyError: - raise sa_exc.ArgumentError( - "Valid strategies for session synchronization " - "are %s" % (", ".join(sorted(repr(x) for x in lookup))) + except KeyError as err: + util.raise_( + sa_exc.ArgumentError( + "Valid strategies for session synchronization " + "are %s" % (", ".join(sorted(repr(x) for x in lookup))) + ), + replace_context=err, ) else: return klass(*arg) @@ -1768,10 +1774,13 @@ class BulkEvaluate(BulkUD): self._additional_evaluators(evaluator_compiler) except evaluator.UnevaluatableError as err: - raise sa_exc.InvalidRequestError( - 'Could not evaluate current criteria in Python: "%s". ' - "Specify 'fetch' or False for the " - "synchronize_session parameter." % err + util.raise_( + sa_exc.InvalidRequestError( + 'Could not evaluate current criteria in Python: "%s". ' + "Specify 'fetch' or False for the " + "synchronize_session parameter." % err + ), + from_=err, ) # TODO: detect when the where clause is a trivial primary key match diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index d237aa3bf..e29e6eeee 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -1019,15 +1019,18 @@ class Query(Generative): for prop in mapper._identity_key_props ) - except KeyError: - raise sa_exc.InvalidRequestError( - "Incorrect names of values in identifier to formulate " - "primary key for query.get(); primary key attribute names" - " are %s" - % ",".join( - "'%s'" % prop.key - for prop in mapper._identity_key_props - ) + except KeyError as err: + util.raise_( + sa_exc.InvalidRequestError( + "Incorrect names of values in identifier to formulate " + "primary key for query.get(); primary key attribute " + "names are %s" + % ",".join( + "'%s'" % prop.key + for prop in mapper._identity_key_props + ) + ), + replace_context=err, ) if ( @@ -3292,9 +3295,12 @@ class Query(Generative): """ try: ret = self.one_or_none() - except orm_exc.MultipleResultsFound: - raise orm_exc.MultipleResultsFound( - "Multiple rows were found for one()" + except orm_exc.MultipleResultsFound as err: + util.raise_( + orm_exc.MultipleResultsFound( + "Multiple rows were found for one()" + ), + replace_context=err, ) else: if ret is None: diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index b82a3d271..2995baf5f 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -2484,50 +2484,64 @@ class JoinCondition(object): a_subset=self.parent_local_selectable, consider_as_foreign_keys=consider_as_foreign_keys, ) - except sa_exc.NoForeignKeysError: + except sa_exc.NoForeignKeysError as nfe: if self.secondary is not None: - raise sa_exc.NoForeignKeysError( - "Could not determine join " - "condition between parent/child tables on " - "relationship %s - there are no foreign keys " - "linking these tables via secondary table '%s'. " - "Ensure that referencing columns are associated " - "with a ForeignKey or ForeignKeyConstraint, or " - "specify 'primaryjoin' and 'secondaryjoin' " - "expressions." % (self.prop, self.secondary) + util.raise_( + sa_exc.NoForeignKeysError( + "Could not determine join " + "condition between parent/child tables on " + "relationship %s - there are no foreign keys " + "linking these tables via secondary table '%s'. " + "Ensure that referencing columns are associated " + "with a ForeignKey or ForeignKeyConstraint, or " + "specify 'primaryjoin' and 'secondaryjoin' " + "expressions." % (self.prop, self.secondary) + ), + from_=nfe, ) else: - raise sa_exc.NoForeignKeysError( - "Could not determine join " - "condition between parent/child tables on " - "relationship %s - there are no foreign keys " - "linking these tables. " - "Ensure that referencing columns are associated " - "with a ForeignKey or ForeignKeyConstraint, or " - "specify a 'primaryjoin' expression." % self.prop + util.raise_( + sa_exc.NoForeignKeysError( + "Could not determine join " + "condition between parent/child tables on " + "relationship %s - there are no foreign keys " + "linking these tables. " + "Ensure that referencing columns are associated " + "with a ForeignKey or ForeignKeyConstraint, or " + "specify a 'primaryjoin' expression." % self.prop + ), + from_=nfe, ) - except sa_exc.AmbiguousForeignKeysError: + except sa_exc.AmbiguousForeignKeysError as afe: if self.secondary is not None: - raise sa_exc.AmbiguousForeignKeysError( - "Could not determine join " - "condition between parent/child tables on " - "relationship %s - there are multiple foreign key " - "paths linking the tables via secondary table '%s'. " - "Specify the 'foreign_keys' " - "argument, providing a list of those columns which " - "should be counted as containing a foreign key " - "reference from the secondary table to each of the " - "parent and child tables." % (self.prop, self.secondary) + util.raise_( + sa_exc.AmbiguousForeignKeysError( + "Could not determine join " + "condition between parent/child tables on " + "relationship %s - there are multiple foreign key " + "paths linking the tables via secondary table '%s'. " + "Specify the 'foreign_keys' " + "argument, providing a list of those columns which " + "should be counted as containing a foreign key " + "reference from the secondary table to each of the " + "parent and child tables." + % (self.prop, self.secondary) + ), + from_=afe, ) else: - raise sa_exc.AmbiguousForeignKeysError( - "Could not determine join " - "condition between parent/child tables on " - "relationship %s - there are multiple foreign key " - "paths linking the tables. Specify the " - "'foreign_keys' argument, providing a list of those " - "columns which should be counted as containing a " - "foreign key reference to the parent table." % self.prop + util.raise_( + sa_exc.AmbiguousForeignKeysError( + "Could not determine join " + "condition between parent/child tables on " + "relationship %s - there are multiple foreign key " + "paths linking the tables. Specify the " + "'foreign_keys' argument, providing a list of those " + "columns which should be counted as containing a " + "foreign key reference to the parent table." + % self.prop + ), + from_=afe, ) @property diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 095033951..74e546483 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -575,7 +575,7 @@ class SessionTransaction(object): self._parent._rollback_exception = sys.exc_info()[1] if rollback_err: - util.reraise(*rollback_err) + util.raise_(rollback_err[1], with_traceback=rollback_err[2]) sess.dispatch.after_soft_rollback(sess, self) @@ -1362,10 +1362,13 @@ class Session(_SessionClassMethods): def _add_bind(self, key, bind): try: insp = inspect(key) - except sa_exc.NoInspectionAvailable: + except sa_exc.NoInspectionAvailable as err: if not isinstance(key, type): - raise sa_exc.ArgumentError( - "Not an acceptable bind target: %s" % key + util.raise_( + sa_exc.ArgumentError( + "Not an acceptable bind target: %s" % key + ), + replace_context=err, ) else: self.__binds[key] = bind @@ -1515,9 +1518,11 @@ class Session(_SessionClassMethods): if mapper is not None: try: mapper = inspect(mapper) - except sa_exc.NoInspectionAvailable: + except sa_exc.NoInspectionAvailable as err: if isinstance(mapper, type): - raise exc.UnmappedClassError(mapper) + util.raise_( + exc.UnmappedClassError(mapper), replace_context=err, + ) else: raise @@ -1656,7 +1661,7 @@ class Session(_SessionClassMethods): "consider using a session.no_autoflush block if this " "flush is occurring prematurely" ) - util.raise_from_cause(e) + util.raise_(e, with_traceback=sys.exc_info[2]) def refresh( self, @@ -1711,8 +1716,10 @@ class Session(_SessionClassMethods): """ try: state = attributes.instance_state(instance) - except exc.NO_STATE: - raise exc.UnmappedInstanceError(instance) + except exc.NO_STATE as err: + util.raise_( + exc.UnmappedInstanceError(instance), replace_context=err, + ) self._expire_state(state, attribute_names) @@ -1817,8 +1824,10 @@ class Session(_SessionClassMethods): """ try: state = attributes.instance_state(instance) - except exc.NO_STATE: - raise exc.UnmappedInstanceError(instance) + except exc.NO_STATE as err: + util.raise_( + exc.UnmappedInstanceError(instance), replace_context=err, + ) self._expire_state(state, attribute_names) def _expire_state(self, state, attribute_names): @@ -1872,8 +1881,10 @@ class Session(_SessionClassMethods): """ try: state = attributes.instance_state(instance) - except exc.NO_STATE: - raise exc.UnmappedInstanceError(instance) + except exc.NO_STATE as err: + util.raise_( + exc.UnmappedInstanceError(instance), replace_context=err, + ) if state.session_id is not self.hash_key: raise sa_exc.InvalidRequestError( "Instance %s is not present in this Session" % state_str(state) @@ -2024,8 +2035,10 @@ class Session(_SessionClassMethods): try: state = attributes.instance_state(instance) - except exc.NO_STATE: - raise exc.UnmappedInstanceError(instance) + except exc.NO_STATE as err: + util.raise_( + exc.UnmappedInstanceError(instance), replace_context=err, + ) self._save_or_update_state(state) @@ -2059,8 +2072,10 @@ class Session(_SessionClassMethods): try: state = attributes.instance_state(instance) - except exc.NO_STATE: - raise exc.UnmappedInstanceError(instance) + except exc.NO_STATE as err: + util.raise_( + exc.UnmappedInstanceError(instance), replace_context=err, + ) self._delete_impl(state, instance, head=True) @@ -2490,8 +2505,10 @@ class Session(_SessionClassMethods): """ try: state = attributes.instance_state(instance) - except exc.NO_STATE: - raise exc.UnmappedInstanceError(instance) + except exc.NO_STATE as err: + util.raise_( + exc.UnmappedInstanceError(instance), replace_context=err, + ) return self._contains_state(state) def __iter__(self): @@ -2586,8 +2603,11 @@ class Session(_SessionClassMethods): for o in objects: try: state = attributes.instance_state(o) - except exc.NO_STATE: - raise exc.UnmappedInstanceError(o) + + except exc.NO_STATE as err: + util.raise_( + exc.UnmappedInstanceError(o), replace_context=err, + ) objset.add(state) else: objset = None @@ -3450,8 +3470,10 @@ def object_session(instance): try: state = attributes.instance_state(instance) - except exc.NO_STATE: - raise exc.UnmappedInstanceError(instance) + except exc.NO_STATE as err: + util.raise_( + exc.UnmappedInstanceError(instance), replace_context=err, + ) else: return _state_session(state) diff --git a/lib/sqlalchemy/orm/strategy_options.py b/lib/sqlalchemy/orm/strategy_options.py index 0c72f3b37..4f7d996d4 100644 --- a/lib/sqlalchemy/orm/strategy_options.py +++ b/lib/sqlalchemy/orm/strategy_options.py @@ -252,11 +252,14 @@ class Load(HasCacheKey, Generative, MapperOption): # use getattr on the class to work around # synonyms, hybrids, etc. attr = getattr(ent.class_, attr) - except AttributeError: + except AttributeError as err: if raiseerr: - raise sa_exc.ArgumentError( - 'Can\'t find property named "%s" on ' - "%s in this Query." % (attr, ent) + util.raise_( + sa_exc.ArgumentError( + 'Can\'t find property named "%s" on ' + "%s in this Query." % (attr, ent) + ), + replace_context=err, ) else: return None diff --git a/lib/sqlalchemy/orm/sync.py b/lib/sqlalchemy/orm/sync.py index 198e64f4f..ceaf54e5d 100644 --- a/lib/sqlalchemy/orm/sync.py +++ b/lib/sqlalchemy/orm/sync.py @@ -13,6 +13,7 @@ between instances based on join conditions. from . import attributes from . import exc from . import util as orm_util +from .. import util def populate( @@ -34,15 +35,15 @@ def populate( value = source.manager[prop.key].impl.get( source, source_dict, attributes.PASSIVE_OFF ) - except exc.UnmappedColumnError: - _raise_col_to_prop(False, source_mapper, l, dest_mapper, r) + except exc.UnmappedColumnError as err: + _raise_col_to_prop(False, source_mapper, l, dest_mapper, r, err) try: # inline of dest_mapper._set_state_attr_by_column prop = dest_mapper._columntoproperty[r] dest.manager[prop.key].impl.set(dest, dest_dict, value, None) - except exc.UnmappedColumnError: - _raise_col_to_prop(True, source_mapper, l, dest_mapper, r) + except exc.UnmappedColumnError as err: + _raise_col_to_prop(True, source_mapper, l, dest_mapper, r, err) # technically the "r.primary_key" check isn't # needed here, but we check for this condition to limit @@ -64,8 +65,8 @@ def bulk_populate_inherit_keys(source_dict, source_mapper, synchronize_pairs): try: prop = source_mapper._columntoproperty[l] value = source_dict[prop.key] - except exc.UnmappedColumnError: - _raise_col_to_prop(False, source_mapper, l, source_mapper, r) + except exc.UnmappedColumnError as err: + _raise_col_to_prop(False, source_mapper, l, source_mapper, r, err) try: prop = source_mapper._columntoproperty[r] @@ -88,8 +89,8 @@ def clear(dest, dest_mapper, synchronize_pairs): ) try: dest_mapper._set_state_attr_by_column(dest, dest.dict, r, None) - except exc.UnmappedColumnError: - _raise_col_to_prop(True, None, l, dest_mapper, r) + except exc.UnmappedColumnError as err: + _raise_col_to_prop(True, None, l, dest_mapper, r, err) def update(source, source_mapper, dest, old_prefix, synchronize_pairs): @@ -101,8 +102,8 @@ def update(source, source_mapper, dest, old_prefix, synchronize_pairs): value = source_mapper._get_state_attr_by_column( source, source.dict, l, passive=attributes.PASSIVE_OFF ) - except exc.UnmappedColumnError: - _raise_col_to_prop(False, source_mapper, l, None, r) + except exc.UnmappedColumnError as err: + _raise_col_to_prop(False, source_mapper, l, None, r, err) dest[r.key] = value dest[old_prefix + r.key] = oldvalue @@ -113,8 +114,8 @@ def populate_dict(source, source_mapper, dict_, synchronize_pairs): value = source_mapper._get_state_attr_by_column( source, source.dict, l, passive=attributes.PASSIVE_OFF ) - except exc.UnmappedColumnError: - _raise_col_to_prop(False, source_mapper, l, None, r) + except exc.UnmappedColumnError as err: + _raise_col_to_prop(False, source_mapper, l, None, r, err) dict_[r.key] = value @@ -127,8 +128,8 @@ def source_modified(uowcommit, source, source_mapper, synchronize_pairs): for l, r in synchronize_pairs: try: prop = source_mapper._columntoproperty[l] - except exc.UnmappedColumnError: - _raise_col_to_prop(False, source_mapper, l, None, r) + except exc.UnmappedColumnError as err: + _raise_col_to_prop(False, source_mapper, l, None, r, err) history = uowcommit.get_attribute_history( source, prop.key, attributes.PASSIVE_NO_INITIALIZE ) @@ -139,22 +140,28 @@ def source_modified(uowcommit, source, source_mapper, synchronize_pairs): def _raise_col_to_prop( - isdest, source_mapper, source_column, dest_mapper, dest_column + isdest, source_mapper, source_column, dest_mapper, dest_column, err ): if isdest: - raise exc.UnmappedColumnError( - "Can't execute sync rule for " - "destination column '%s'; mapper '%s' does not map " - "this column. Try using an explicit `foreign_keys` " - "collection which does not include this column (or use " - "a viewonly=True relation)." % (dest_column, dest_mapper) + util.raise_( + exc.UnmappedColumnError( + "Can't execute sync rule for " + "destination column '%s'; mapper '%s' does not map " + "this column. Try using an explicit `foreign_keys` " + "collection which does not include this column (or use " + "a viewonly=True relation)." % (dest_column, dest_mapper) + ), + replace_context=err, ) else: - raise exc.UnmappedColumnError( - "Can't execute sync rule for " - "source column '%s'; mapper '%s' does not map this " - "column. Try using an explicit `foreign_keys` " - "collection which does not include destination column " - "'%s' (or use a viewonly=True relation)." - % (source_column, source_mapper, dest_column) + util.raise_( + exc.UnmappedColumnError( + "Can't execute sync rule for " + "source column '%s'; mapper '%s' does not map this " + "column. Try using an explicit `foreign_keys` " + "collection which does not include destination column " + "'%s' (or use a viewonly=True relation)." + % (source_column, source_mapper, dest_column) + ), + replace_context=err, ) |