summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2013-06-17 18:48:17 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2013-06-17 19:42:43 -0400
commit7f82168cb6b0f0e22d387ffeca1ae82f526c2f29 (patch)
tree1ef9195524e6de9fc1e49e07795398538ac20043 /lib/sqlalchemy/orm
parent42d58f8b6e67c01827f1eed283e23067bbdb848d (diff)
downloadsqlalchemy-7f82168cb6b0f0e22d387ffeca1ae82f526c2f29.tar.gz
- 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]
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/attributes.py17
-rw-r--r--lib/sqlalchemy/orm/descriptor_props.py19
-rw-r--r--lib/sqlalchemy/orm/interfaces.py21
-rw-r--r--lib/sqlalchemy/orm/properties.py19
-rw-r--r--lib/sqlalchemy/orm/util.py181
5 files changed, 137 insertions, 120 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index bfba695b8..2627a1c44 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 97ca6d2eb..c58951339 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 396f234c4..150277be2 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 8c0576527..5986556db 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 ef9de760e..617ef6085 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 '<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__)