diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-08-12 20:37:40 -0400 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-08-12 20:37:40 -0400 |
commit | daf286fc33e4008499f5aea14dc44630c3709c11 (patch) | |
tree | a2bf9820ee138247c6cde76f3b59f3d6d50707cf /lib/sqlalchemy/orm/attributes.py | |
parent | 964b2bcccfbc55103b126d04fd609c858ad211bd (diff) | |
download | sqlalchemy-daf286fc33e4008499f5aea14dc44630c3709c11.tar.gz |
- [bug] Fixed bug whereby user error in related-object
assignment could cause recursion overflow if the
assignment triggered a backref of the same name
as a bi-directional attribute on the incorrect
class to the same target. An informative
error is raised now.
Diffstat (limited to 'lib/sqlalchemy/orm/attributes.py')
-rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 56 |
1 files changed, 38 insertions, 18 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 2e576b4d8..d26ee61c3 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -983,6 +983,14 @@ def backref_listeners(attribute, key, uselist): # use easily recognizable names for stack traces + parent_token = attribute.impl.parent_token + + def _acceptable_key_err(child_state, initiator): + raise ValueError( + "Object %s not associated with attribute of " + "type %s" % (orm_util.state_str(child_state), + manager_of_class(initiator.class_)[initiator.key])) + def emit_backref_from_scalar_set_event(state, child, oldchild, initiator): if oldchild is child: return child @@ -1001,35 +1009,47 @@ def backref_listeners(attribute, key, uselist): if child is not None: child_state, child_dict = instance_state(child),\ instance_dict(child) - child_state.manager[key].impl.append( - child_state, - child_dict, - state.obj(), - initiator, - passive=PASSIVE_NO_FETCH) + child_impl = child_state.manager[key].impl + if initiator.parent_token is not parent_token and \ + initiator.parent_token is not child_impl.parent_token: + _acceptable_key_err(state, initiator) + child_impl.append( + child_state, + child_dict, + state.obj(), + initiator, + passive=PASSIVE_NO_FETCH) return child def emit_backref_from_collection_append_event(state, child, initiator): child_state, child_dict = instance_state(child), \ instance_dict(child) - child_state.manager[key].impl.append( - child_state, - child_dict, - state.obj(), - initiator, - passive=PASSIVE_NO_FETCH) + child_impl = child_state.manager[key].impl + if initiator.parent_token is not parent_token and \ + initiator.parent_token is not child_impl.parent_token: + _acceptable_key_err(state, initiator) + child_impl.append( + child_state, + child_dict, + state.obj(), + initiator, + passive=PASSIVE_NO_FETCH) return child def emit_backref_from_collection_remove_event(state, child, initiator): if child is not None: child_state, child_dict = instance_state(child),\ instance_dict(child) - child_state.manager[key].impl.pop( - child_state, - child_dict, - state.obj(), - initiator, - passive=PASSIVE_NO_FETCH) + child_impl = child_state.manager[key].impl + # can't think of a path that would produce an initiator + # mismatch here, as it would require an existing collection + # mismatch. + child_impl.pop( + child_state, + child_dict, + state.obj(), + initiator, + passive=PASSIVE_NO_FETCH) if uselist: event.listen(attribute, "append", |