diff options
Diffstat (limited to 'lib/sqlalchemy/ext/associationproxy.py')
| -rw-r--r-- | lib/sqlalchemy/ext/associationproxy.py | 100 |
1 files changed, 81 insertions, 19 deletions
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py index 1c28b10a1..629b4ac64 100644 --- a/lib/sqlalchemy/ext/associationproxy.py +++ b/lib/sqlalchemy/ext/associationproxy.py @@ -17,6 +17,7 @@ import operator from .. import exc, orm, util from ..orm import collections, interfaces from ..sql import or_ +from ..sql.operators import ColumnOperators from .. import inspect @@ -217,7 +218,7 @@ class AssociationProxy(interfaces.InspectionAttrInfo): except KeyError: owner = self._calc_owner(class_) if owner is not None: - result = AssociationProxyInstance(self, owner) + result = AssociationProxyInstance.for_proxy(self, owner) setattr(class_, self.key + "_inst", result) return result else: @@ -283,13 +284,49 @@ class AssociationProxyInstance(object): """ - def __init__(self, parent, owning_class): + def __init__(self, parent, owning_class, target_class, value_attr): self.parent = parent self.key = parent.key self.owning_class = owning_class self.target_collection = parent.target_collection self.value_attr = parent.value_attr self.collection_class = None + self.target_class = target_class + self.value_attr = value_attr + + target_class = None + """The intermediary class handled by this + :class:`.AssociationProxyInstance`. + + Intercepted append/set/assignment events will result + in the generation of new instances of this class. + + """ + + @classmethod + def for_proxy(cls, parent, owning_class): + target_collection = parent.target_collection + value_attr = parent.value_attr + prop = orm.class_mapper(owning_class).\ + get_property(target_collection) + target_class = prop.mapper.class_ + + target_assoc = cls._cls_unwrap_target_assoc_proxy( + target_class, value_attr) + if target_assoc is not None: + return ObjectAssociationProxyInstance( + parent, owning_class, target_class, value_attr + ) + + is_object = getattr(target_class, value_attr).impl.uses_objects + if is_object: + return ObjectAssociationProxyInstance( + parent, owning_class, target_class, value_attr + ) + else: + return ColumnAssociationProxyInstance( + parent, owning_class, target_class, value_attr + ) def _get_property(self): return orm.class_mapper(self.owning_class).\ @@ -299,13 +336,18 @@ class AssociationProxyInstance(object): def _comparator(self): return self._get_property().comparator - @util.memoized_property - def _unwrap_target_assoc_proxy(self): - attr = getattr(self.target_class, self.value_attr) + @classmethod + def _cls_unwrap_target_assoc_proxy(cls, target_class, value_attr): + attr = getattr(target_class, value_attr) if isinstance(attr, (AssociationProxy, AssociationProxyInstance)): return attr return None + @util.memoized_property + def _unwrap_target_assoc_proxy(self): + return self._cls_unwrap_target_assoc_proxy( + self.target_class, self.value_attr) + @property def remote_attr(self): """The 'remote' :class:`.MapperProperty` referenced by this @@ -353,17 +395,6 @@ class AssociationProxyInstance(object): return (self.local_attr, self.remote_attr) @util.memoized_property - def target_class(self): - """The intermediary class handled by this - :class:`.AssociationProxyInstance`. - - Intercepted append/set/assignment events will result - in the generation of new instances of this class. - - """ - return self._get_property().mapper.class_ - - @util.memoized_property def scalar(self): """Return ``True`` if this :class:`.AssociationProxyInstance` proxies a scalar relationship on the local side.""" @@ -378,9 +409,9 @@ class AssociationProxyInstance(object): return not self._get_property().\ mapper.get_property(self.value_attr).uselist - @util.memoized_property + @property def _target_is_object(self): - return getattr(self.target_class, self.value_attr).impl.uses_objects + raise NotImplementedError() def _initialize_scalar_accessors(self): if self.parent.getset_factory: @@ -587,6 +618,12 @@ class AssociationProxyInstance(object): return self._criterion_exists( criterion=criterion, is_has=True, **kwargs) + +class ObjectAssociationProxyInstance(AssociationProxyInstance): + """an :class:`.AssociationProxyInstance` that has an object as a target. + """ + _target_is_object = True + def contains(self, obj): """Produce a proxied 'contains' expression using EXISTS. @@ -611,7 +648,7 @@ class AssociationProxyInstance(object): elif self._target_is_object and self.scalar and \ self._value_is_scalar: raise exc.InvalidRequestError( - "contains() doesn't apply to a scalar endpoint; use ==") + "contains() doesn't apply to a scalar object endpoint; use ==") else: return self._comparator._criterion_exists(**{self.value_attr: obj}) @@ -634,6 +671,31 @@ class AssociationProxyInstance(object): getattr(self.target_class, self.value_attr) != obj) +class ColumnAssociationProxyInstance( + ColumnOperators, AssociationProxyInstance): + """an :class:`.AssociationProxyInstance` that has a database column as a + target. + """ + _target_is_object = False + + def __eq__(self, other): + # special case "is None" to check for no related row as well + expr = self._criterion_exists( + self.remote_attr.operate(operator.eq, other) + ) + if other is None: + return or_( + expr, self._comparator == None + ) + else: + return expr + + def operate(self, op, *other, **kwargs): + return self._criterion_exists( + self.remote_attr.operate(op, *other, **kwargs) + ) + + class _lazy_collection(object): def __init__(self, obj, target): self.parent = obj |
