]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- rework PropComparator.adapted() to be PropComparator.adapt_to_entity(),
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 17 Jun 2013 22:48:17 +0000 (18:48 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 17 Jun 2013 23:42:43 +0000 (19:42 -0400)
passes in AliasedInsp and allows more flexibility.
- rework the AliasedClass/AliasedInsp relationship so that AliasedInsp has
all state and functionality.  AliasedClass is just a facade.
[ticket:2756]

lib/sqlalchemy/ext/hybrid.py
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/descriptor_props.py
lib/sqlalchemy/orm/interfaces.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/util.py
test/orm/test_inspect.py

index 98da5ad36682199a3d50191853e674d195d5624d..59e5a74cb8ea1a97af3367c5968cc8c108aeaec8 100644 (file)
@@ -803,6 +803,6 @@ class Comparator(interfaces.PropComparator):
             expr = expr.__clause_element__()
         return expr
 
-    def adapted(self, adapter):
+    def adapt_to_entity(self, adapt_to_entity):
         # interesting....
         return self
index bfba695b843098663d255e944af78d3de5f838d1..2627a1c4475b39099cd542f5e49ed515eca7e59b 100644 (file)
@@ -335,14 +335,14 @@ def create_proxied_attribute(descriptor):
 
         def __init__(self, class_, key, descriptor,
                                 comparator,
-                                adapter=None, doc=None,
+                                adapt_to_entity=None, doc=None,
                                 original_property=None):
             self.class_ = class_
             self.key = key
             self.descriptor = descriptor
             self.original_property = original_property
             self._comparator = comparator
-            self.adapter = adapter
+            self._adapt_to_entity = adapt_to_entity
             self.__doc__ = doc
 
         @property
@@ -353,18 +353,15 @@ def create_proxied_attribute(descriptor):
         def comparator(self):
             if util.callable(self._comparator):
                 self._comparator = self._comparator()
-            if self.adapter:
-                self._comparator = self._comparator.adapted(self.adapter)
+            if self._adapt_to_entity:
+                self._comparator = self._comparator.adapt_to_entity(
+                                    self._adapt_to_entity)
             return self._comparator
 
-        def adapted(self, adapter):
-            """Proxy adapted() for the use case of AliasedClass calling
-            adapted.
-
-            """
+        def adapt_to_entity(self, adapt_to_entity):
             return self.__class__(self.class_, self.key, self.descriptor,
                                        self._comparator,
-                                       adapter)
+                                       adapt_to_entity)
 
         def __get__(self, instance, owner):
             if instance is None:
index 97ca6d2eb0b6f265d2f0f929116ef77be488fefa..c589513393713cf5eb2f142b4831b4fe41e146bf 100644 (file)
@@ -326,16 +326,13 @@ class CompositeProperty(DescriptorProperty):
 
         @util.memoized_property
         def _comparable_elements(self):
-            if self.adapter:
-                # we need to do a little fudging here because
-                # the adapter function we're given only accepts
-                # ColumnElements, but our prop._comparable_elements is returning
-                # InstrumentedAttribute, because we support the use case
-                # of composites that refer to relationships.  The better
-                # solution here is to open up how AliasedClass interacts
-                # with PropComparators so more context is available.
-                return [self.adapter(x.__clause_element__())
-                            for x in self.prop._comparable_elements]
+            if self._adapt_to_entity:
+                return [
+                        getattr(
+                                self._adapt_to_entity.entity,
+                                prop.key
+                        ) for prop in self.prop._comparable_elements
+                        ]
             else:
                 return self.prop._comparable_elements
 
@@ -348,7 +345,7 @@ class CompositeProperty(DescriptorProperty):
                 a == b
                 for a, b in zip(self.prop._comparable_elements, values)
             ]
-            if self.adapter:
+            if self._adapt_to_entity:
                 comparisons = [self.adapter(x) for x in comparisons]
             return sql.and_(*comparisons)
 
index 396f234c43db4e19699ac1546d9d4e7e3e67facb..150277be2081c81ef4d753a8b5fe8f546b06e99d 100644 (file)
@@ -381,21 +381,30 @@ class PropComparator(operators.ColumnOperators):
 
     """
 
-    def __init__(self, prop, parentmapper, adapter=None):
+    def __init__(self, prop, parentmapper, adapt_to_entity=None):
         self.prop = self.property = prop
         self._parentmapper = parentmapper
-        self.adapter = adapter
+        self._adapt_to_entity = adapt_to_entity
 
     def __clause_element__(self):
         raise NotImplementedError("%r" % self)
 
-    def adapted(self, adapter):
+    def adapt_to_entity(self, adapt_to_entity):
         """Return a copy of this PropComparator which will use the given
-        adaption function on the local side of generated expressions.
-
+        :class:`.AliasedInsp` to produce corresponding expressions.
         """
+        return self.__class__(self.prop, self._parentmapper, adapt_to_entity)
+
+    @property
+    def adapter(self):
+        """Produce a callable that adapts column expressions
+        to suit an aliased version of this comparator.
 
-        return self.__class__(self.prop, self._parentmapper, adapter)
+        """
+        if self._adapt_to_entity is None:
+            return None
+        else:
+            return self._adapt_to_entity._adapt_element
 
     @util.memoized_property
     def info(self):
index 8c0576527cabec76451ba6207461e25b15279da1..5986556db652b3b1ef105de48729e9291b76b1e3 100644 (file)
@@ -355,27 +355,21 @@ class RelationshipProperty(StrategizedProperty):
 
         _of_type = None
 
-        def __init__(self, prop, parentmapper, of_type=None, adapter=None):
+        def __init__(self, prop, parentmapper, adapt_to_entity=None, of_type=None):
             """Construction of :class:`.RelationshipProperty.Comparator`
             is internal to the ORM's attribute mechanics.
 
             """
             self.prop = prop
             self._parentmapper = parentmapper
-            self.adapter = adapter
+            self._adapt_to_entity = adapt_to_entity
             if of_type:
                 self._of_type = of_type
 
-        def adapted(self, adapter):
-            """Return a copy of this PropComparator which will use the
-            given adaption function on the local side of generated
-            expressions.
-
-            """
-
+        def adapt_to_entity(self, adapt_to_entity):
             return self.__class__(self.property, self._parentmapper,
-                                  getattr(self, '_of_type', None),
-                                  adapter)
+                                  adapt_to_entity=adapt_to_entity,
+                                    of_type=self._of_type)
 
         @util.memoized_property
         def mapper(self):
@@ -427,7 +421,8 @@ class RelationshipProperty(StrategizedProperty):
             return RelationshipProperty.Comparator(
                                         self.property,
                                         self._parentmapper,
-                                        cls, adapter=self.adapter)
+                                        adapt_to_entity=self._adapt_to_entity,
+                                        of_type=cls)
 
         def in_(self, other):
             """Produce an IN clause - this is not implemented
index ef9de760e93b433e8e965881e0b20922f9be57c8..617ef6085aa2ebcfd557472d85695819e55a5a0c 100644 (file)
@@ -516,88 +516,32 @@ class AliasedClass(object):
                 if with_polymorphic_discriminator is not None
                 else mapper.polymorphic_on,
             base_alias,
-            use_mapper_path
+            use_mapper_path,
+            adapt_on_names
         )
 
-        self._setup(self._aliased_insp, adapt_on_names)
-
-
-    def _setup(self, aliased_insp, adapt_on_names):
-        self.__adapt_on_names = adapt_on_names
-        mapper = aliased_insp.mapper
-        alias = aliased_insp.selectable
-        self.__target = mapper.class_
-        self.__adapt_on_names = adapt_on_names
-        self.__adapter = sql_util.ClauseAdapter(alias,
-                            equivalents=mapper._equivalent_columns,
-                            adapt_on_names=self.__adapt_on_names)
-        for poly in aliased_insp.with_polymorphic_mappers:
-            if poly is not mapper:
-                setattr(self, poly.class_.__name__,
-                    AliasedClass(poly.class_, alias, base_alias=self,
-                            use_mapper_path=self._aliased_insp._use_mapper_path))
-
-        self.__name__ = 'AliasedClass_%s' % self.__target.__name__
-
-    def __getstate__(self):
-        return {
-            'mapper': self._aliased_insp.mapper,
-            'alias': self._aliased_insp.selectable,
-            'name': self._aliased_insp.name,
-            'adapt_on_names': self.__adapt_on_names,
-            'with_polymorphic_mappers':
-                self._aliased_insp.with_polymorphic_mappers,
-            'with_polymorphic_discriminator':
-                self._aliased_insp.polymorphic_on,
-            'base_alias': self._aliased_insp._base_alias.entity,
-            'use_mapper_path': self._aliased_insp._use_mapper_path
-        }
-
-    def __setstate__(self, state):
-        self._aliased_insp = AliasedInsp(
-            self,
-            state['mapper'],
-            state['alias'],
-            state['name'],
-            state['with_polymorphic_mappers'],
-            state['with_polymorphic_discriminator'],
-            state['base_alias'],
-            state['use_mapper_path']
-        )
-        self._setup(self._aliased_insp, state['adapt_on_names'])
-
-    def __adapt_element(self, elem):
-        return self.__adapter.traverse(elem).\
-                    _annotate({
-                        'parententity': self,
-                        'parentmapper': self._aliased_insp.mapper}
-                    )
-
-    def __adapt_prop(self, existing, key):
-        comparator = existing.comparator.adapted(self.__adapt_element)
-        queryattr = attributes.QueryableAttribute(
-                                self, key,
-                                impl=existing.impl,
-                                parententity=self._aliased_insp,
-                                comparator=comparator)
-        setattr(self, key, queryattr)
-        return queryattr
+        self.__name__ = 'AliasedClass_%s' % mapper.class_.__name__
 
     def __getattr__(self, key):
-        for base in self.__target.__mro__:
-            try:
-                attr = object.__getattribute__(base, key)
-            except AttributeError:
-                continue
-            else:
-                break
+        try:
+            _aliased_insp = self.__dict__['_aliased_insp']
+        except KeyError:
+            raise AttributeError()
         else:
-            raise AttributeError(key)
+            for base in _aliased_insp._target.__mro__:
+                try:
+                    attr = object.__getattribute__(base, key)
+                except AttributeError:
+                    continue
+                else:
+                    break
+            else:
+                raise AttributeError(key)
 
         if isinstance(attr, attributes.QueryableAttribute):
-            return self.__adapt_prop(attr, key)
+            return _aliased_insp._adapt_prop(attr, key)
         elif hasattr(attr, 'func_code'):
-            is_method = getattr(self.__target, key, None)
+            is_method = getattr(_aliased_insp._target, key, None)
             if is_method and is_method.__self__ is not None:
                 return util.types.MethodType(attr.__func__, self, self)
             else:
@@ -605,14 +549,14 @@ class AliasedClass(object):
         elif hasattr(attr, '__get__'):
             ret = attr.__get__(None, self)
             if isinstance(ret, PropComparator):
-                return ret.adapted(self.__adapt_element)
+                return ret.adapt_to_entity(_aliased_insp)
             return ret
         else:
             return attr
 
     def __repr__(self):
         return '<AliasedClass at 0x%x; %s>' % (
-            id(self), self.__target.__name__)
+            id(self), self._aliased_insp._target.__name__)
 
 
 class AliasedInsp(_InspectionAttr):
@@ -653,19 +597,29 @@ class AliasedInsp(_InspectionAttr):
 
     def __init__(self, entity, mapper, selectable, name,
                     with_polymorphic_mappers, polymorphic_on,
-                    _base_alias, _use_mapper_path):
+                    _base_alias, _use_mapper_path, adapt_on_names):
         self.entity = entity
         self.mapper = mapper
         self.selectable = selectable
         self.name = name
         self.with_polymorphic_mappers = with_polymorphic_mappers
         self.polymorphic_on = polymorphic_on
-
-        # a little dance to get serialization to work
-        self._base_alias = _base_alias._aliased_insp if _base_alias \
-                            and _base_alias is not entity else self
+        self._base_alias = _base_alias or self
         self._use_mapper_path = _use_mapper_path
 
+        self._adapter = sql_util.ClauseAdapter(selectable,
+                            equivalents=mapper._equivalent_columns,
+                            adapt_on_names=adapt_on_names)
+
+        self._adapt_on_names = adapt_on_names
+        self._target = mapper.class_
+
+        for poly in self.with_polymorphic_mappers:
+            if poly is not mapper:
+                setattr(self.entity, poly.class_.__name__,
+                    AliasedClass(poly.class_, selectable, base_alias=self,
+                            adapt_on_names=adapt_on_names,
+                            use_mapper_path=_use_mapper_path))
 
     is_aliased_class = True
     "always returns True"
@@ -683,6 +637,52 @@ class AliasedInsp(_InspectionAttr):
         else:
             return PathRegistry.per_mapper(self)
 
+    def __getstate__(self):
+        return {
+            'entity': self.entity,
+            'mapper': self.mapper,
+            'alias': self.selectable,
+            'name': self.name,
+            'adapt_on_names': self._adapt_on_names,
+            'with_polymorphic_mappers':
+                self.with_polymorphic_mappers,
+            'with_polymorphic_discriminator':
+                self.polymorphic_on,
+            'base_alias': self._base_alias,
+            'use_mapper_path': self._use_mapper_path
+        }
+
+    def __setstate__(self, state):
+        self.__init__(
+            state['entity'],
+            state['mapper'],
+            state['alias'],
+            state['name'],
+            state['with_polymorphic_mappers'],
+            state['with_polymorphic_discriminator'],
+            state['base_alias'],
+            state['use_mapper_path'],
+            state['adapt_on_names']
+        )
+
+    def _adapt_element(self, elem):
+        return self._adapter.traverse(elem).\
+                    _annotate({
+                        'parententity': self.entity,
+                        'parentmapper': self.mapper}
+                    )
+
+    def _adapt_prop(self, existing, key):
+        comparator = existing.comparator.adapt_to_entity(self)
+        queryattr = attributes.QueryableAttribute(
+                                self.entity, key,
+                                impl=existing.impl,
+                                parententity=self,
+                                comparator=comparator)
+        setattr(self.entity, key, queryattr)
+        return queryattr
+
+
     def _entity_for_mapper(self, mapper):
         self_poly = self.with_polymorphic_mappers
         if mapper in self_poly:
@@ -692,6 +692,25 @@ class AliasedInsp(_InspectionAttr):
         else:
             assert False, "mapper %s doesn't correspond to %s" % (mapper, self)
 
+    def _adapt_element(self, elem):
+        return self._adapter.traverse(elem).\
+                    _annotate({
+                        'parententity': self.entity,
+                        'parentmapper': self.mapper}
+                    )
+
+    def _adapt_prop(self, existing, key):
+        comparator = existing.comparator.adapt_to_entity(self)
+        queryattr = attributes.QueryableAttribute(
+                                self.entity, key,
+                                impl=existing.impl,
+                                parententity=self,
+                                comparator=comparator)
+        setattr(self.entity, key, queryattr)
+        return queryattr
+
+
+
     def __repr__(self):
         return '<AliasedInsp at 0x%x; %s>' % (
             id(self), self.class_.__name__)
index 78d84f505f15004079f0a3e079ead163b277c076..61c1fd93eb6d06eaddec0897989a263470fc33c0 100644 (file)
@@ -81,7 +81,7 @@ class TestORMInspection(_fixtures.FixtureTest):
         ualias = aliased(Address)
         insp = inspect(ualias)
         is_(insp.mapper, inspect(Address))
-        is_(insp.selectable, ualias._AliasedClass__adapter.selectable)
+        is_(insp.selectable, ualias._aliased_insp.selectable)
         assert not insp.is_selectable
         assert insp.is_aliased_class