From: Mike Bayer Date: Mon, 17 Jun 2013 22:48:17 +0000 (-0400) Subject: - rework PropComparator.adapted() to be PropComparator.adapt_to_entity(), X-Git-Tag: rel_0_9_0b1~252 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7f82168cb6b0f0e22d387ffeca1ae82f526c2f29;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - rework PropComparator.adapted() to be PropComparator.adapt_to_entity(), 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] --- diff --git a/lib/sqlalchemy/ext/hybrid.py b/lib/sqlalchemy/ext/hybrid.py index 98da5ad366..59e5a74cb8 100644 --- a/lib/sqlalchemy/ext/hybrid.py +++ b/lib/sqlalchemy/ext/hybrid.py @@ -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 diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index bfba695b84..2627a1c447 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -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: diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index 97ca6d2eb0..c589513393 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -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) diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 396f234c43..150277be20 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -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): diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 8c0576527c..5986556db6 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -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 diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index ef9de760e9..617ef6085a 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -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 '' % ( - 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 '' % ( id(self), self.class_.__name__) diff --git a/test/orm/test_inspect.py b/test/orm/test_inspect.py index 78d84f505f..61c1fd93eb 100644 --- a/test/orm/test_inspect.py +++ b/test/orm/test_inspect.py @@ -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