diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-12-02 12:40:50 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2013-12-02 12:40:50 -0500 |
| commit | 50e3847f09580d1e322fb11f54983e9a31846f19 (patch) | |
| tree | 726356a797ffc807cefb58e6c224c2e2729cc6d0 /lib/sqlalchemy/orm | |
| parent | d80ee72aaa4b7f8a23e1bd55515b8446a951a5f0 (diff) | |
| download | sqlalchemy-50e3847f09580d1e322fb11f54983e9a31846f19.tar.gz | |
- Added new argument ``include_backrefs=True`` to the
:func:`.validates` function; when set to False, a validation event
will not be triggered if the event was initated as a backref to
an attribute operation from the other side. [ticket:1535]
- break out validation tests into an updated module test_validators
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 22 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 4 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 31 |
3 files changed, 45 insertions, 12 deletions
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 375e7b1af..9b91d0638 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -1103,11 +1103,10 @@ class Mapper(_InspectionAttr): self._reconstructor = method event.listen(manager, 'load', _event_on_load, raw=True) elif hasattr(method, '__sa_validators__'): - include_removes = getattr(method, - "__sa_include_removes__", False) + validation_opts = method.__sa_validation_opts__ for name in method.__sa_validators__: self.validators = self.validators.union( - {name: (method, include_removes)} + {name: (method, validation_opts)} ) manager.info[_INSTRUMENTOR] = self @@ -2582,13 +2581,28 @@ def validates(*names, **kw): argument "is_remove" which will be a boolean. .. versionadded:: 0.7.7 + :param include_backrefs: defaults to ``True``; if ``False``, the + validation function will not emit if the originator is an attribute + event related via a backref. This can be used for bi-directional + :func:`.validates` usage where only one validator should emit per + attribute operation. + + .. versionadded:: 0.9.0b2 + + .. seealso:: + + :ref:`simple_validators` - usage examples for :func:`.validates` """ include_removes = kw.pop('include_removes', False) + include_backrefs = kw.pop('include_backrefs', True) def wrap(fn): fn.__sa_validators__ = names - fn.__sa_include_removes__ = include_removes + fn.__sa_validation_opts__ = { + "include_removes": include_removes, + "include_backrefs": include_backrefs + } return fn return wrap diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index b04338d9c..8226a0e0f 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -44,10 +44,10 @@ def _register_attribute(strategy, mapper, useobject, listen_hooks.append(single_parent_validator) if prop.key in prop.parent.validators: - fn, include_removes = prop.parent.validators[prop.key] + fn, opts = prop.parent.validators[prop.key] listen_hooks.append( lambda desc, prop: orm_util._validator_events(desc, - prop.key, fn, include_removes) + prop.key, fn, **opts) ) if useobject: diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 1b8f53c9d..b86672175 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -70,24 +70,43 @@ class CascadeOptions(frozenset): ) -def _validator_events(desc, key, validator, include_removes): +def _validator_events(desc, key, validator, include_removes, include_backrefs): """Runs a validation method on an attribute value to be set or appended.""" + if not include_backrefs: + def detect_is_backref(state, initiator): + impl = state.manager[key].impl + return initiator.impl is not impl + if include_removes: def append(state, value, initiator): - return validator(state.obj(), key, value, False) + if include_backrefs or not detect_is_backref(state, initiator): + return validator(state.obj(), key, value, False) + else: + return value def set_(state, value, oldvalue, initiator): - return validator(state.obj(), key, value, False) + if include_backrefs or not detect_is_backref(state, initiator): + return validator(state.obj(), key, value, False) + else: + return value def remove(state, value, initiator): - validator(state.obj(), key, value, True) + if include_backrefs or not detect_is_backref(state, initiator): + validator(state.obj(), key, value, True) + else: def append(state, value, initiator): - return validator(state.obj(), key, value) + if include_backrefs or not detect_is_backref(state, initiator): + return validator(state.obj(), key, value) + else: + return value def set_(state, value, oldvalue, initiator): - return validator(state.obj(), key, value) + if include_backrefs or not detect_is_backref(state, initiator): + return validator(state.obj(), key, value) + else: + return value event.listen(desc, 'append', append, raw=True, retval=True) event.listen(desc, 'set', set_, raw=True, retval=True) |
