From: Mike Bayer Date: Mon, 20 Dec 2010 22:50:57 +0000 (-0500) Subject: - convert built in AttributExtensions to event listener fns X-Git-Tag: rel_0_7b1~134 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=86e35dfdf6a9c1e715609a9452fef1e1811c8cf2;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - convert built in AttributExtensions to event listener fns --- diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 63331e0813..259f6e7e74 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -31,7 +31,6 @@ from sqlalchemy.orm.interfaces import ( ) from sqlalchemy.orm.util import ( AliasedClass as aliased, - Validator, join, object_mapper, outerjoin, @@ -63,7 +62,6 @@ __all__ = ( 'InstrumentationManager', 'MapperExtension', 'AttributeExtension', - 'Validator', 'PropComparator', 'Query', 'Session', diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 667e69190e..ffabf0bdc9 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -1147,7 +1147,7 @@ def register_attribute_impl(class_, key, backref_listeners(manager[key], backref, uselist) manager.post_configure_attribute(key) - + return manager[key] def register_descriptor(class_, key, comparator=None, parententity=None, property_=None, doc=None): diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 1f704f5023..88a8a8ea66 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -1605,8 +1605,6 @@ class Session(object): _expire_state = state.InstanceState.expire_attributes -UOWEventHandler = unitofwork.UOWEventHandler - _sessions = weakref.WeakValueDictionary() def make_transient(instance): diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 21f22ef509..ec2c3c2bc3 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -8,7 +8,7 @@ implementations, and related MapperOptions.""" from sqlalchemy import exc as sa_exc -from sqlalchemy import sql, util, log +from sqlalchemy import sql, util, log, event from sqlalchemy.sql import util as sql_util from sqlalchemy.sql import visitors, expression, operators from sqlalchemy.orm import mapper, attributes, interfaces, exc as orm_exc @@ -17,7 +17,7 @@ from sqlalchemy.orm.interfaces import ( LoaderStrategy, StrategizedOption, MapperOption, PropertyOption, serialize_path, deserialize_path, StrategizedProperty ) -from sqlalchemy.orm import session as sessionlib +from sqlalchemy.orm import session as sessionlib, unitofwork from sqlalchemy.orm import util as mapperutil from sqlalchemy.orm.query import Query import itertools @@ -38,22 +38,36 @@ def _register_attribute(strategy, mapper, useobject, prop = strategy.parent_property attribute_ext = list(util.to_list(prop.extension, default=[])) - + + listen_hooks = [] + if useobject and prop.single_parent: - attribute_ext.insert(0, _SingleParentValidator(prop)) + listen_hooks.append(single_parent_validator) if prop.key in prop.parent._validators: - attribute_ext.insert(0, - mapperutil.Validator(prop.key, prop.parent._validators[prop.key]) + listen_hooks.append( + lambda desc, prop: mapperutil._validator_events(desc, + prop.key, + prop.parent._validators[prop.key]) ) if useobject: - attribute_ext.append(sessionlib.UOWEventHandler(prop.key)) + listen_hooks.append(unitofwork.track_cascade_events) + # need to assemble backref listeners + # after the singleparentvalidator, mapper validator + backref = kw.pop('backref', None) + if backref: + listen_hooks.append( + lambda desc, prop: attributes.backref_listeners(desc, + backref, + uselist) + ) + for m in mapper.self_and_descendants: if prop is m._props.get(prop.key): - attributes.register_attribute_impl( + desc = attributes.register_attribute_impl( m.class_, prop.key, parent_token=prop, @@ -71,6 +85,9 @@ def _register_attribute(strategy, mapper, useobject, doc=prop.doc, **kw ) + + for hook in listen_hooks: + hook(desc, prop) class UninstrumentedColumnLoader(LoaderStrategy): """Represent the a non-instrumented MapperProperty. @@ -1237,11 +1254,8 @@ class LoadEagerFromAliasOption(PropertyOption): ("user_defined_eager_row_processor", interfaces._reduce_path(paths[-1]))] = adapter -class _SingleParentValidator(interfaces.AttributeExtension): - def __init__(self, prop): - self.prop = prop - - def _do_check(self, state, value, oldvalue, initiator): +def single_parent_validator(desc, prop): + def _do_check(state, value, oldvalue, initiator): if value is not None: hasparent = initiator.hasparent(attributes.instance_state(value)) if hasparent and oldvalue is not value: @@ -1249,14 +1263,16 @@ class _SingleParentValidator(interfaces.AttributeExtension): "Instance %s is already associated with an instance " "of %s via its %s attribute, and is only allowed a " "single parent." % - (mapperutil.instance_str(value), state.class_, self.prop) + (mapperutil.instance_str(value), state.class_, prop) ) return value - def append(self, state, value, initiator): - return self._do_check(state, value, None, initiator) - - def set(self, state, value, oldvalue, initiator): - return self._do_check(state, value, oldvalue, initiator) - + def append(state, value, initiator): + return _do_check(state, value, None, initiator) + def set_(state, value, oldvalue, initiator): + return _do_check(state, value, oldvalue, initiator) + + event.listen(desc, 'on_append', append, raw=True, retval=True, active_history=True) + event.listen(desc, 'on_set', set_, raw=True, retval=True, active_history=True) + diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index ba43b13592..0dd5640a88 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -12,42 +12,37 @@ organizes them in order of dependency, and executes. """ -from sqlalchemy import util +from sqlalchemy import util, event from sqlalchemy.util import topological from sqlalchemy.orm import attributes, interfaces from sqlalchemy.orm import util as mapperutil session = util.importlater("sqlalchemy.orm", "session") -class UOWEventHandler(interfaces.AttributeExtension): - """An event handler added to all relationship attributes which handles - session cascade operations. - """ - - active_history = False +def track_cascade_events(descriptor, prop): + """Establish event listeners on object attributes which handle + cascade-on-set/append. - def __init__(self, key): - self.key = key - - # TODO: migrate these to unwrapped events + """ + key = prop.key - def append(self, state, item, initiator): + def append(state, item, initiator): # process "save_update" cascade rules for when # an instance is appended to the list of another instance sess = session._state_session(state) if sess: - prop = state.manager.mapper._props[self.key] + prop = state.manager.mapper._props[key] item_state = attributes.instance_state(item) if prop.cascade.save_update and \ - (prop.cascade_backrefs or self.key == initiator.key) and \ + (prop.cascade_backrefs or key == initiator.key) and \ not sess._contains_state(item_state): sess._save_or_update_state(item_state) return item - def remove(self, state, item, initiator): + def remove(state, item, initiator): sess = session._state_session(state) if sess: - prop = state.manager.mapper._props[self.key] + prop = state.manager.mapper._props[key] # expunge pending orphans item_state = attributes.instance_state(item) if prop.cascade.delete_orphan and \ @@ -55,7 +50,7 @@ class UOWEventHandler(interfaces.AttributeExtension): prop.mapper._is_orphan(item_state): sess.expunge(item) - def set(self, state, newvalue, oldvalue, initiator): + def set_(state, newvalue, oldvalue, initiator): # process "save_update" cascade rules for when an instance # is attached to another instance if oldvalue is newvalue: @@ -63,11 +58,11 @@ class UOWEventHandler(interfaces.AttributeExtension): sess = session._state_session(state) if sess: - prop = state.manager.mapper._props[self.key] + prop = state.manager.mapper._props[key] if newvalue is not None: newvalue_state = attributes.instance_state(newvalue) if prop.cascade.save_update and \ - (prop.cascade_backrefs or self.key == initiator.key) and \ + (prop.cascade_backrefs or key == initiator.key) and \ not sess._contains_state(newvalue_state): sess._save_or_update_state(newvalue_state) @@ -78,6 +73,10 @@ class UOWEventHandler(interfaces.AttributeExtension): prop.mapper._is_orphan(oldvalue_state): sess.expunge(oldvalue) return newvalue + + event.listen(descriptor, 'on_append', append, raw=True, retval=True) + event.listen(descriptor, 'on_remove', remove, raw=True, retval=True) + event.listen(descriptor, 'on_set', set_, raw=True, retval=True) class UOWTransaction(object): diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index b5fa0c0cff..52e250239b 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -6,11 +6,10 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php import sqlalchemy.exceptions as sa_exc -from sqlalchemy import sql, util +from sqlalchemy import sql, util, event from sqlalchemy.sql import expression, util as sql_util, operators from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE,\ - PropComparator, MapperProperty,\ - AttributeExtension + PropComparator, MapperProperty from sqlalchemy.orm import attributes, exc import operator @@ -55,37 +54,18 @@ class CascadeOptions(dict): 'delete_orphan', 'refresh-expire'] if getattr(self, x, False) is True])) +def _validator_events(desc, key, validator): + """Runs a validation method on an attribute value to be set or appended.""" -class Validator(AttributeExtension): - """Runs a validation method on an attribute value to be set or appended. - - The Validator class is used by the :func:`~sqlalchemy.orm.validates` - decorator, and direct access is usually not needed. - - """ - - def __init__(self, key, validator): - """Construct a new Validator. - - key - name of the attribute to be validated; - will be passed as the second argument to - the validation method (the first is the object instance itself). - - validator - an function or instance method which accepts - three arguments; an instance (usually just 'self' for a method), - the key name of the attribute, and the value. The function should - return the same value given, unless it wishes to modify it. - - """ - self.key = key - self.validator = validator - - def append(self, state, value, initiator): - return self.validator(state.obj(), self.key, value) - - def set(self, state, value, oldvalue, initiator): - return self.validator(state.obj(), self.key, value) + def append(state, value, initiator): + return validator(state.obj(), key, value) + def set_(state, value, oldvalue, initiator): + return validator(state.obj(), key, value) + + event.listen(desc, 'on_append', append, raw=True, retval=True) + event.listen(desc, 'on_set', set_, raw=True, retval=True) + def polymorphic_union(table_map, typecolname, aliasname='p_union'): """Create a ``UNION`` statement used by a polymorphic mapper.