summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2013-12-02 12:40:50 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2013-12-02 12:40:50 -0500
commit50e3847f09580d1e322fb11f54983e9a31846f19 (patch)
tree726356a797ffc807cefb58e6c224c2e2729cc6d0 /lib/sqlalchemy/orm
parentd80ee72aaa4b7f8a23e1bd55515b8446a951a5f0 (diff)
downloadsqlalchemy-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.py22
-rw-r--r--lib/sqlalchemy/orm/strategies.py4
-rw-r--r--lib/sqlalchemy/orm/util.py31
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)