summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/properties.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2008-05-09 16:34:10 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2008-05-09 16:34:10 +0000
commit4a6afd469fad170868554bf28578849bf3dfd5dd (patch)
treeb396edc33d567ae19dd244e87137296450467725 /lib/sqlalchemy/orm/properties.py
parent46b7c9dc57a38d5b9e44a4723dad2ad8ec57baca (diff)
downloadsqlalchemy-4a6afd469fad170868554bf28578849bf3dfd5dd.tar.gz
r4695 merged to trunk; trunk now becomes 0.5.
0.4 development continues at /sqlalchemy/branches/rel_0_4
Diffstat (limited to 'lib/sqlalchemy/orm/properties.py')
-rw-r--r--lib/sqlalchemy/orm/properties.py362
1 files changed, 196 insertions, 166 deletions
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py
index 33a0ff432..fc2e90189 100644
--- a/lib/sqlalchemy/orm/properties.py
+++ b/lib/sqlalchemy/orm/properties.py
@@ -6,19 +6,20 @@
"""MapperProperty implementations.
-This is a private module which defines the behavior of
-invidual ORM-mapped attributes.
+This is a private module which defines the behavior of invidual ORM-mapped
+attributes.
+
"""
-from sqlalchemy import sql, schema, util, exceptions, logging
-from sqlalchemy.sql.util import ClauseAdapter, criterion_as_pairs, find_columns
-from sqlalchemy.sql import visitors, operators, ColumnElement
-from sqlalchemy.orm import mapper, sync, strategies, attributes, dependency, object_mapper
-from sqlalchemy.orm import session as sessionlib
-from sqlalchemy.orm.mapper import _class_to_mapper
-from sqlalchemy.orm.util import CascadeOptions, PropertyAliasedClauses
-from sqlalchemy.orm.interfaces import StrategizedProperty, PropComparator, MapperProperty, ONETOMANY, MANYTOONE, MANYTOMANY
-from sqlalchemy.exceptions import ArgumentError
+from sqlalchemy import sql, util, log
+import sqlalchemy.exceptions as sa_exc
+from sqlalchemy.sql.util import ClauseAdapter, criterion_as_pairs
+from sqlalchemy.sql import operators, ColumnElement, expression
+from sqlalchemy.orm import mapper, strategies, attributes, dependency, \
+ object_mapper, session as sessionlib
+from sqlalchemy.orm.util import CascadeOptions, _class_to_mapper, _orm_annotate
+from sqlalchemy.orm.interfaces import StrategizedProperty, PropComparator, \
+ MapperProperty, ONETOMANY, MANYTOONE, MANYTOMANY
__all__ = ('ColumnProperty', 'CompositeProperty', 'SynonymProperty',
'ComparableProperty', 'PropertyLoader', 'BackRef')
@@ -34,18 +35,15 @@ class ColumnProperty(StrategizedProperty):
appears across each table.
"""
- self.columns = list(columns)
+ self.columns = [expression._labeled(c) for c in columns]
self.group = kwargs.pop('group', None)
self.deferred = kwargs.pop('deferred', False)
self.comparator = ColumnProperty.ColumnComparator(self)
+ util.set_creation_order(self)
if self.deferred:
self.strategy_class = strategies.DeferredColumnLoader
else:
self.strategy_class = strategies.ColumnLoader
- # sanity check
- for col in columns:
- if not isinstance(col, ColumnElement):
- raise ArgumentError('column_property() must be given a ColumnElement as its argument. Try .label() or .as_scalar() for Selectables to fix this.')
def do_init(self):
super(ColumnProperty, self).do_init()
@@ -61,37 +59,41 @@ class ColumnProperty(StrategizedProperty):
return ColumnProperty(deferred=self.deferred, group=self.group, *self.columns)
def getattr(self, state, column):
- return getattr(state.class_, self.key).impl.get(state)
+ return state.get_impl(self.key).get(state)
def getcommitted(self, state, column):
- return getattr(state.class_, self.key).impl.get_committed_value(state)
+ return state.get_impl(self.key).get_committed_value(state)
def setattr(self, state, value, column):
- getattr(state.class_, self.key).impl.set(state, value, None)
+ state.get_impl(self.key).set(state, value, None)
def merge(self, session, source, dest, dont_load, _recursive):
- value = attributes.get_as_list(source._state, self.key, passive=True)
+ value = attributes.instance_state(source).value_as_iterable(
+ self.key, passive=True)
if value:
setattr(dest, self.key, value[0])
else:
- # TODO: lazy callable should merge to the new instance
- dest._state.expire_attributes([self.key])
+ attributes.instance_state(dest).expire_attributes([self.key])
def get_col_value(self, column, value):
return value
class ColumnComparator(PropComparator):
- def clause_element(self):
- return self.prop.columns[0]
-
+ def __clause_element__(self):
+ return self.prop.columns[0]._annotate({"parententity": self.prop.parent})
+ __clause_element__ = util.cache_decorator(__clause_element__)
+
def operate(self, op, *other, **kwargs):
- return op(self.prop.columns[0], *other, **kwargs)
+ return op(self.__clause_element__(), *other, **kwargs)
def reverse_operate(self, op, other, **kwargs):
- col = self.prop.columns[0]
+ col = self.__clause_element__()
return op(col._bind_param(other), col, **kwargs)
-ColumnProperty.logger = logging.class_logger(ColumnProperty)
+ def __str__(self):
+ return str(self.parent.class_.__name__) + "." + self.key
+
+ColumnProperty.logger = log.class_logger(ColumnProperty)
class CompositeProperty(ColumnProperty):
"""subclasses ColumnProperty to provide composite type support."""
@@ -100,6 +102,7 @@ class CompositeProperty(ColumnProperty):
super(CompositeProperty, self).__init__(*columns, **kwargs)
self.composite_class = class_
self.comparator = kwargs.pop('comparator', CompositeProperty.Comparator)(self)
+ self.strategy_class = strategies.CompositeColumnLoader
def do_init(self):
super(ColumnProperty, self).do_init()
@@ -109,19 +112,19 @@ class CompositeProperty(ColumnProperty):
return CompositeProperty(deferred=self.deferred, group=self.group, composite_class=self.composite_class, *self.columns)
def getattr(self, state, column):
- obj = getattr(state.class_, self.key).impl.get(state)
+ obj = state.get_impl(self.key).get(state)
return self.get_col_value(column, obj)
def getcommitted(self, state, column):
- obj = getattr(state.class_, self.key).impl.get_committed_value(state)
+ obj = state.get_impl(self.key).get_committed_value(state)
return self.get_col_value(column, obj)
def setattr(self, state, value, column):
# TODO: test coverage for this method
- obj = getattr(state.class_, self.key).impl.get(state)
+ obj = state.get_impl(self.key).get(state)
if obj is None:
obj = self.composite_class(*[None for c in self.columns])
- getattr(state.class_, self.key).impl.set(state, obj, None)
+ state.get_impl(self.key).set(state, obj, None)
for a, b in zip(self.columns, value.__composite_values__()):
if a is column:
@@ -133,6 +136,9 @@ class CompositeProperty(ColumnProperty):
return b
class Comparator(PropComparator):
+ def __clause_element__(self):
+ return expression.ClauseList(*self.prop.columns)
+
def __eq__(self, other):
if other is None:
return sql.and_(*[a==None for a in self.prop.columns])
@@ -146,17 +152,21 @@ class CompositeProperty(ColumnProperty):
zip(self.prop.columns,
other.__composite_values__())])
+ def __str__(self):
+ return str(self.parent.class_.__name__) + "." + self.key
+
class SynonymProperty(MapperProperty):
def __init__(self, name, map_column=None, descriptor=None):
self.name = name
- self.map_column=map_column
+ self.map_column = map_column
self.descriptor = descriptor
+ util.set_creation_order(self)
- def setup(self, querycontext, **kwargs):
+ def setup(self, context, entity, path, adapter, **kwargs):
pass
- def create_row_processor(self, selectcontext, mapper, row):
- return (None, None, None)
+ def create_row_processor(self, selectcontext, path, mapper, row, adapter):
+ return (None, None)
def do_init(self):
class_ = self.parent.class_
@@ -174,12 +184,11 @@ class SynonymProperty(MapperProperty):
return s
return getattr(obj, self.name)
self.descriptor = SynonymProp()
- sessionlib.register_attribute(class_, self.key, uselist=False, proxy_property=self.descriptor, useobject=False, comparator=comparator)
+ sessionlib.register_attribute(class_, self.key, uselist=False, proxy_property=self.descriptor, useobject=False, comparator=comparator, parententity=self.parent)
def merge(self, session, source, dest, _recursive):
pass
-SynonymProperty.logger = logging.class_logger(SynonymProperty)
-
+SynonymProperty.logger = log.class_logger(SynonymProperty)
class ComparableProperty(MapperProperty):
"""Instruments a Python property for use in query expressions."""
@@ -187,6 +196,7 @@ class ComparableProperty(MapperProperty):
def __init__(self, comparator_factory, descriptor=None):
self.descriptor = descriptor
self.comparator = comparator_factory(self)
+ util.set_creation_order(self)
def do_init(self):
"""Set up a proxy to the unmanaged descriptor."""
@@ -198,11 +208,11 @@ class ComparableProperty(MapperProperty):
useobject=False,
comparator=self.comparator)
- def setup(self, querycontext, **kwargs):
+ def setup(self, context, entity, path, adapter, **kwargs):
pass
- def create_row_processor(self, selectcontext, mapper, row):
- return (None, None, None)
+ def create_row_processor(self, selectcontext, path, mapper, row, adapter):
+ return (None, None)
class PropertyLoader(StrategizedProperty):
@@ -210,7 +220,22 @@ class PropertyLoader(StrategizedProperty):
of items that correspond to a related database table.
"""
- def __init__(self, argument, secondary=None, primaryjoin=None, secondaryjoin=None, entity_name=None, foreign_keys=None, foreignkey=None, uselist=None, private=False, association=None, order_by=False, attributeext=None, backref=None, is_backref=False, post_update=False, cascade=None, viewonly=False, lazy=True, collection_class=None, passive_deletes=False, passive_updates=True, remote_side=None, enable_typechecks=True, join_depth=None, strategy_class=None, _local_remote_pairs=None):
+ def __init__(self, argument,
+ secondary=None, primaryjoin=None,
+ secondaryjoin=None, entity_name=None,
+ foreign_keys=None,
+ uselist=None,
+ order_by=False,
+ backref=None,
+ _is_backref=False,
+ post_update=False,
+ cascade=None,
+ viewonly=False, lazy=True,
+ collection_class=None, passive_deletes=False,
+ passive_updates=True, remote_side=None,
+ enable_typechecks=True, join_depth=None,
+ strategy_class=None, _local_remote_pairs=None):
+
self.uselist = uselist
self.argument = argument
self.entity_name = entity_name
@@ -222,9 +247,6 @@ class PropertyLoader(StrategizedProperty):
self.viewonly = viewonly
self.lazy = lazy
self.foreign_keys = util.to_set(foreign_keys)
- self._legacy_foreignkey = util.to_set(foreignkey)
- if foreignkey:
- util.warn_deprecated('foreignkey option is deprecated; see docs for details')
self.collection_class = collection_class
self.passive_deletes = passive_deletes
self.passive_updates = passive_updates
@@ -233,6 +255,8 @@ class PropertyLoader(StrategizedProperty):
self.comparator = PropertyLoader.Comparator(self)
self.join_depth = join_depth
self._arg_local_remote_pairs = _local_remote_pairs
+ self.__join_cache = {}
+ util.set_creation_order(self)
if strategy_class:
self.strategy_class = strategy_class
@@ -251,20 +275,13 @@ class PropertyLoader(StrategizedProperty):
if cascade is not None:
self.cascade = CascadeOptions(cascade)
else:
- if private:
- util.warn_deprecated('private option is deprecated; see docs for details')
- self.cascade = CascadeOptions("all, delete-orphan")
- else:
- self.cascade = CascadeOptions("save-update, merge")
+ self.cascade = CascadeOptions("save-update, merge")
if self.passive_deletes == 'all' and ("delete" in self.cascade or "delete-orphan" in self.cascade):
- raise exceptions.ArgumentError("Can't set passive_deletes='all' in conjunction with 'delete' or 'delete-orphan' cascade")
+ raise sa_exc.ArgumentError("Can't set passive_deletes='all' in conjunction with 'delete' or 'delete-orphan' cascade")
- self.association = association
- if association:
- util.warn_deprecated('association option is deprecated; see docs for details')
self.order_by = order_by
- self.attributeext=attributeext
+
if isinstance(backref, str):
# propigate explicitly sent primary/secondary join conditions to the BackRef object if
# just a string was sent
@@ -275,14 +292,21 @@ class PropertyLoader(StrategizedProperty):
self.backref = BackRef(backref, primaryjoin=primaryjoin, secondaryjoin=secondaryjoin, passive_updates=self.passive_updates)
else:
self.backref = backref
- self.is_backref = is_backref
-
+ self._is_backref = _is_backref
+
class Comparator(PropComparator):
def __init__(self, prop, of_type=None):
self.prop = self.property = prop
if of_type:
self._of_type = _class_to_mapper(of_type)
+ def parententity(self):
+ return self.prop.parent
+ parententity = property(parententity)
+
+ def __clause_element__(self):
+ return self.prop.parent._with_polymorphic_selectable
+
def of_type(self, cls):
return PropertyLoader.Comparator(self.prop, cls)
@@ -294,7 +318,7 @@ class PropertyLoader(StrategizedProperty):
return self.prop._optimized_compare(None)
elif self.prop.uselist:
if not hasattr(other, '__iter__'):
- raise exceptions.InvalidRequestError("Can only compare a collection to an iterable object. Use contains().")
+ raise sa_exc.InvalidRequestError("Can only compare a collection to an iterable object. Use contains().")
else:
j = self.prop.primaryjoin
if self.prop.secondaryjoin:
@@ -308,60 +332,62 @@ class PropertyLoader(StrategizedProperty):
else:
return self.prop._optimized_compare(other)
- def _join_and_criterion(self, criterion=None, **kwargs):
+ def __criterion_exists(self, criterion=None, **kwargs):
if getattr(self, '_of_type', None):
target_mapper = self._of_type
- to_selectable = target_mapper._with_polymorphic_selectable() #mapped_table
+ to_selectable = target_mapper._with_polymorphic_selectable
else:
to_selectable = None
- pj, sj, source, dest, target_adapter = self.prop._create_joins(dest_polymorphic=True, dest_selectable=to_selectable)
+ pj, sj, source, dest, secondary, target_adapter = self.prop._create_joins(dest_polymorphic=True, dest_selectable=to_selectable)
for k in kwargs:
- crit = (getattr(self.prop.mapper.class_, k) == kwargs[k])
+ crit = self.prop.mapper.class_manager.get_inst(k) == kwargs[k]
if criterion is None:
criterion = crit
else:
criterion = criterion & crit
if sj:
- j = pj & sj
+ j = _orm_annotate(pj) & sj
else:
- j = pj
+ j = _orm_annotate(pj, exclude=self.prop.remote_side)
if criterion and target_adapter:
+ # limit this adapter to annotated only?
criterion = target_adapter.traverse(criterion)
- return j, criterion, dest
+ # only have the "joined left side" of what we return be subject to Query adaption. The right
+ # side of it is used for an exists() subquery and should not correlate or otherwise reach out
+ # to anything in the enclosing query.
+ if criterion:
+ criterion = criterion._annotate({'_halt_adapt': True})
+ return sql.exists([1], j & criterion, from_obj=dest).correlate(source)
def any(self, criterion=None, **kwargs):
if not self.prop.uselist:
- raise exceptions.InvalidRequestError("'any()' not implemented for scalar attributes. Use has().")
- j, criterion, from_obj = self._join_and_criterion(criterion, **kwargs)
+ raise sa_exc.InvalidRequestError("'any()' not implemented for scalar attributes. Use has().")
- return sql.exists([1], j & criterion, from_obj=from_obj)
+ return self.__criterion_exists(criterion, **kwargs)
def has(self, criterion=None, **kwargs):
if self.prop.uselist:
- raise exceptions.InvalidRequestError("'has()' not implemented for collections. Use any().")
- j, criterion, from_obj = self._join_and_criterion(criterion, **kwargs)
-
- return sql.exists([1], j & criterion, from_obj=from_obj)
+ raise sa_exc.InvalidRequestError("'has()' not implemented for collections. Use any().")
+ return self.__criterion_exists(criterion, **kwargs)
def contains(self, other):
if not self.prop.uselist:
- raise exceptions.InvalidRequestError("'contains' not implemented for scalar attributes. Use ==")
+ raise sa_exc.InvalidRequestError("'contains' not implemented for scalar attributes. Use ==")
clause = self.prop._optimized_compare(other)
if self.prop.secondaryjoin:
- clause.negation_clause = self._negated_contains_or_equals(other)
+ clause.negation_clause = self.__negated_contains_or_equals(other)
return clause
- def _negated_contains_or_equals(self, other):
+ def __negated_contains_or_equals(self, other):
criterion = sql.and_(*[x==y for (x, y) in zip(self.prop.mapper.primary_key, self.prop.mapper.primary_key_from_instance(other))])
- j, criterion, from_obj = self._join_and_criterion(criterion)
- return ~sql.exists([1], j & criterion, from_obj=from_obj)
+ return ~self.__criterion_exists(criterion)
def __ne__(self, other):
if other is None:
@@ -373,9 +399,9 @@ class PropertyLoader(StrategizedProperty):
return self.has()
if self.prop.uselist and not hasattr(other, '__iter__'):
- raise exceptions.InvalidRequestError("Can only compare a collection to an iterable object")
+ raise sa_exc.InvalidRequestError("Can only compare a collection to an iterable object")
- return self._negated_contains_or_equals(other)
+ return self.__negated_contains_or_equals(other)
def compare(self, op, value, value_is_parent=False):
if op == operators.eq:
@@ -390,27 +416,29 @@ class PropertyLoader(StrategizedProperty):
return op(self.comparator, value)
def _optimized_compare(self, value, value_is_parent=False):
+ if value is not None:
+ value = attributes.instance_state(value)
return self._get_strategy(strategies.LazyLoader).lazy_clause(value, reverse_direction=not value_is_parent)
- def private(self):
- return self.cascade.delete_orphan
- private = property(private)
-
def __str__(self):
- return str(self.parent.class_.__name__) + "." + self.key + " (" + str(self.mapper.class_.__name__) + ")"
+ return str(self.parent.class_.__name__) + "." + self.key
def merge(self, session, source, dest, dont_load, _recursive):
if not dont_load and self._reverse_property and (source, self._reverse_property) in _recursive:
return
-
+
+ source_state = attributes.instance_state(source)
+ dest_state = attributes.instance_state(dest)
+
if not "merge" in self.cascade:
- dest._state.expire_attributes([self.key])
+ dest_state.expire_attributes([self.key])
return
- instances = attributes.get_as_list(source._state, self.key, passive=True)
+ instances = source_state.value_as_iterable(self.key, passive=True)
+
if not instances:
return
-
+
if self.uselist:
dest_list = []
for current in instances:
@@ -419,11 +447,11 @@ class PropertyLoader(StrategizedProperty):
if obj is not None:
dest_list.append(obj)
if dont_load:
- coll = attributes.init_collection(dest, self.key)
+ coll = attributes.init_collection(dest_state, self.key)
for c in dest_list:
coll.append_without_event(c)
else:
- getattr(dest.__class__, self.key).impl._set_iterable(dest._state, dest_list)
+ getattr(dest.__class__, self.key).impl._set_iterable(dest_state, dest_list)
else:
current = instances[0]
if current is not None:
@@ -440,17 +468,17 @@ class PropertyLoader(StrategizedProperty):
return
passive = type_ != 'delete' or self.passive_deletes
mapper = self.mapper.primary_mapper()
- instances = attributes.get_as_list(state, self.key, passive=passive)
+ instances = state.value_as_iterable(self.key, passive=passive)
if instances:
for c in instances:
if c is not None and c not in visited_instances and (halt_on is None or not halt_on(c)):
if not isinstance(c, self.mapper.class_):
- raise exceptions.AssertionError("Attribute '%s' on class '%s' doesn't handle objects of type '%s'" % (self.key, str(self.parent.class_), str(c.__class__)))
+ raise AssertionError("Attribute '%s' on class '%s' doesn't handle objects of type '%s'" % (self.key, str(self.parent.class_), str(c.__class__)))
visited_instances.add(c)
# cascade using the mapper local to this object, so that its individual properties are located
instance_mapper = object_mapper(c, entity_name=mapper.entity_name)
- yield (c, instance_mapper, c._state)
+ yield (c, instance_mapper, attributes.instance_state(c))
def _get_target_class(self):
"""Return the target class of the relation, even if the
@@ -479,7 +507,8 @@ class PropertyLoader(StrategizedProperty):
# accept a callable to suit various deferred-configurational schemes
self.mapper = mapper.class_mapper(self.argument(), entity_name=self.entity_name, compile=False)
else:
- raise exceptions.ArgumentError("relation '%s' expects a class or a mapper argument (received: %s)" % (self.key, type(self.argument)))
+ raise sa_exc.ArgumentError("relation '%s' expects a class or a mapper argument (received: %s)" % (self.key, type(self.argument)))
+ assert isinstance(self.mapper, mapper.Mapper), self.mapper
if not self.parent.concrete:
for inheriting in self.parent.iterate_to_root():
@@ -495,14 +524,14 @@ class PropertyLoader(StrategizedProperty):
if self.cascade.delete_orphan:
if self.parent.class_ is self.mapper.class_:
- raise exceptions.ArgumentError("In relationship '%s', can't establish 'delete-orphan' cascade "
+ raise sa_exc.ArgumentError("In relationship '%s', can't establish 'delete-orphan' cascade "
"rule on a self-referential relationship. "
"You probably want cascade='all', which includes delete cascading but not orphan detection." %(str(self)))
self.mapper.primary_mapper().delete_orphans.append((self.key, self.parent.class_))
def __determine_joins(self):
if self.secondaryjoin is not None and self.secondary is None:
- raise exceptions.ArgumentError("Property '" + self.key + "' specified with secondary join condition but no secondary argument")
+ raise sa_exc.ArgumentError("Property '" + self.key + "' specified with secondary join condition but no secondary argument")
# if join conditions were not specified, figure them out based on foreign keys
def _search_for_join(mapper, table):
@@ -512,7 +541,7 @@ class PropertyLoader(StrategizedProperty):
is a join."""
try:
return sql.join(mapper.local_table, table)
- except exceptions.ArgumentError, e:
+ except sa_exc.ArgumentError, e:
return sql.join(mapper.mapped_table, table)
try:
@@ -524,8 +553,8 @@ class PropertyLoader(StrategizedProperty):
else:
if self.primaryjoin is None:
self.primaryjoin = _search_for_join(self.parent, self.target).onclause
- except exceptions.ArgumentError, e:
- raise exceptions.ArgumentError("Could not determine join condition between parent/child tables on relation %s. "
+ except sa_exc.ArgumentError, e:
+ raise sa_exc.ArgumentError("Could not determine join condition between parent/child tables on relation %s. "
"Specify a 'primaryjoin' expression. If this is a many-to-many relation, 'secondaryjoin' is needed as well." % (self))
@@ -540,14 +569,11 @@ class PropertyLoader(StrategizedProperty):
def __determine_fks(self):
- if self._legacy_foreignkey and not self._refers_to_parent_table():
- self.foreign_keys = self._legacy_foreignkey
-
arg_foreign_keys = self.foreign_keys
if self._arg_local_remote_pairs:
if not arg_foreign_keys:
- raise exceptions.ArgumentError("foreign_keys argument is required with _local_remote_pairs argument")
+ raise sa_exc.ArgumentError("foreign_keys argument is required with _local_remote_pairs argument")
self.foreign_keys = util.OrderedSet(arg_foreign_keys)
self._opposite_side = util.OrderedSet()
for l, r in self._arg_local_remote_pairs:
@@ -562,15 +588,15 @@ class PropertyLoader(StrategizedProperty):
if not eq_pairs:
if not self.viewonly and criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=arg_foreign_keys, any_operator=True):
- raise exceptions.ArgumentError("Could not locate any equated, locally mapped column pairs for primaryjoin condition '%s' on relation %s. "
+ raise sa_exc.ArgumentError("Could not locate any equated, locally mapped column pairs for primaryjoin condition '%s' on relation %s. "
"For more relaxed rules on join conditions, the relation may be marked as viewonly=True." % (self.primaryjoin, self)
)
else:
if arg_foreign_keys:
- raise exceptions.ArgumentError("Could not determine relation direction for primaryjoin condition '%s', on relation %s. "
+ raise sa_exc.ArgumentError("Could not determine relation direction for primaryjoin condition '%s', on relation %s. "
"Specify _local_remote_pairs=[(local, remote), (local, remote), ...] to explicitly establish the local/remote column pairs." % (self.primaryjoin, self))
else:
- raise exceptions.ArgumentError("Could not determine relation direction for primaryjoin condition '%s', on relation %s. "
+ raise sa_exc.ArgumentError("Could not determine relation direction for primaryjoin condition '%s', on relation %s. "
"Specify the foreign_keys argument to indicate which columns on the relation are foreign." % (self.primaryjoin, self))
self.foreign_keys = util.OrderedSet([r for l, r in eq_pairs])
@@ -583,11 +609,11 @@ class PropertyLoader(StrategizedProperty):
if not sq_pairs:
if not self.viewonly and criterion_as_pairs(self.secondaryjoin, consider_as_foreign_keys=arg_foreign_keys, any_operator=True):
- raise exceptions.ArgumentError("Could not locate any equated, locally mapped column pairs for secondaryjoin condition '%s' on relation %s. "
+ raise sa_exc.ArgumentError("Could not locate any equated, locally mapped column pairs for secondaryjoin condition '%s' on relation %s. "
"For more relaxed rules on join conditions, the relation may be marked as viewonly=True." % (self.secondaryjoin, self)
)
else:
- raise exceptions.ArgumentError("Could not determine relation direction for secondaryjoin condition '%s', on relation %s. "
+ raise sa_exc.ArgumentError("Could not determine relation direction for secondaryjoin condition '%s', on relation %s. "
"Specify the foreign_keys argument to indicate which columns on the relation are foreign." % (self.secondaryjoin, self))
self.foreign_keys.update([r for l, r in sq_pairs])
@@ -599,7 +625,7 @@ class PropertyLoader(StrategizedProperty):
def __determine_remote_side(self):
if self._arg_local_remote_pairs:
if self.remote_side:
- raise exceptions.ArgumentError("remote_side argument is redundant against more detailed _local_remote_side argument.")
+ raise sa_exc.ArgumentError("remote_side argument is redundant against more detailed _local_remote_side argument.")
if self.direction is MANYTOONE:
eq_pairs = [(r, l) for l, r in self._arg_local_remote_pairs]
else:
@@ -629,11 +655,11 @@ class PropertyLoader(StrategizedProperty):
if self.direction is ONETOMANY:
for l in self.local_side:
if not self.__col_is_part_of_mappings(l):
- raise exceptions.ArgumentError("Local column '%s' is not part of mapping %s. Specify remote_side argument to indicate which column lazy join condition should compare against." % (l, self.parent))
+ raise sa_exc.ArgumentError("Local column '%s' is not part of mapping %s. Specify remote_side argument to indicate which column lazy join condition should compare against." % (l, self.parent))
elif self.direction is MANYTOONE:
for r in self.remote_side:
if not self.__col_is_part_of_mappings(r):
- raise exceptions.ArgumentError("Remote column '%s' is not part of mapping %s. Specify remote_side argument to indicate which column lazy join condition should bind." % (r, self.mapper))
+ raise sa_exc.ArgumentError("Remote column '%s' is not part of mapping %s. Specify remote_side argument to indicate which column lazy join condition should bind." % (r, self.mapper))
def __determine_direction(self):
"""Determine our *direction*, i.e. do we represent one to
@@ -646,13 +672,7 @@ class PropertyLoader(StrategizedProperty):
# for a self referential mapper, if the "foreignkey" is a single or composite primary key,
# then we are "many to one", since the remote site of the relationship identifies a singular entity.
# otherwise we are "one to many".
- if self._legacy_foreignkey:
- for f in self._legacy_foreignkey:
- if not f.primary_key:
- self.direction = ONETOMANY
- else:
- self.direction = MANYTOONE
- elif self._arg_local_remote_pairs:
+ if self._arg_local_remote_pairs:
remote = util.Set([r for l, r in self._arg_local_remote_pairs])
if self.foreign_keys.intersection(remote):
self.direction = ONETOMANY
@@ -671,7 +691,7 @@ class PropertyLoader(StrategizedProperty):
manytoone = [c for c in self.foreign_keys if parenttable.c.contains_column(c)]
if not onetomany and not manytoone:
- raise exceptions.ArgumentError(
+ raise sa_exc.ArgumentError(
"Can't determine relation direction for relationship '%s' "
"- foreign key columns are present in neither the "
"parent nor the child's mapped tables" %(str(self)))
@@ -684,14 +704,14 @@ class PropertyLoader(StrategizedProperty):
self.direction = MANYTOONE
break
else:
- raise exceptions.ArgumentError(
+ raise sa_exc.ArgumentError(
"Can't determine relation direction for relationship '%s' "
"- foreign key columns are present in both the parent and "
"the child's mapped tables. Specify 'foreign_keys' "
"argument." % (str(self)))
def _post_init(self):
- if logging.is_info_enabled(self.logger):
+ if log.is_info_enabled(self.logger):
self.logger.info(str(self) + " setup primary join %s" % self.primaryjoin)
self.logger.info(str(self) + " setup secondary join %s" % self.secondaryjoin)
self.logger.info(str(self) + " synchronize pairs [%s]" % ",".join(["(%s => %s)" % (l, r) for l, r in self.synchronize_pairs]))
@@ -710,15 +730,10 @@ class PropertyLoader(StrategizedProperty):
# primary property handler, set up class attributes
if self.is_primary():
- # if a backref name is defined, set up an extension to populate
- # attributes in the other direction
- if self.backref is not None:
- self.attributeext = self.backref.get_extension()
-
if self.backref is not None:
self.backref.compile(self)
elif not mapper.class_mapper(self.parent.class_, compile=False)._get_property(self.key, raiseerr=False):
- raise exceptions.ArgumentError("Attempting to assign a new relation '%s' to a non-primary mapper on class '%s'. New relations can only be added to the primary mapper, i.e. the very first mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__))
+ raise sa_exc.ArgumentError("Attempting to assign a new relation '%s' to a non-primary mapper on class '%s'. New relations can only be added to the primary mapper, i.e. the very first mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__))
super(PropertyLoader, self).do_init()
@@ -729,50 +744,69 @@ class PropertyLoader(StrategizedProperty):
return self.mapper.common_parent(self.parent)
def _create_joins(self, source_polymorphic=False, source_selectable=None, dest_polymorphic=False, dest_selectable=None):
+ key = util.WeakCompositeKey(source_polymorphic, source_selectable, dest_polymorphic, dest_selectable)
+ try:
+ return self.__join_cache[key]
+ except KeyError:
+ pass
+
if source_selectable is None:
if source_polymorphic and self.parent.with_polymorphic:
- source_selectable = self.parent._with_polymorphic_selectable()
- else:
- source_selectable = None
+ source_selectable = self.parent._with_polymorphic_selectable
+
+ aliased = False
if dest_selectable is None:
if dest_polymorphic and self.mapper.with_polymorphic:
- dest_selectable = self.mapper._with_polymorphic_selectable()
+ dest_selectable = self.mapper._with_polymorphic_selectable
+ aliased = True
else:
dest_selectable = self.mapper.mapped_table
- if self._is_self_referential():
+
+ if self._is_self_referential() and source_selectable is None:
+ dest_selectable = dest_selectable.alias()
+ aliased = True
+ else:
+ aliased = True
+
+ aliased = aliased or bool(source_selectable)
+
+ primaryjoin, secondaryjoin, secondary = self.primaryjoin, self.secondaryjoin, self.secondary
+ if aliased:
+ if secondary:
+ secondary = secondary.alias()
+ primary_aliasizer = ClauseAdapter(secondary)
if dest_selectable:
- dest_selectable = dest_selectable.alias()
+ secondary_aliasizer = ClauseAdapter(dest_selectable, equivalents=self.mapper._equivalent_columns).chain(primary_aliasizer)
else:
- dest_selectable = self.mapper.mapped_table.alias()
-
- primaryjoin = self.primaryjoin
- if source_selectable:
- if self.direction in (ONETOMANY, MANYTOMANY):
- primaryjoin = ClauseAdapter(source_selectable, exclude=self.foreign_keys, equivalents=self.parent._equivalent_columns).traverse(primaryjoin)
+ secondary_aliasizer = primary_aliasizer
+
+ if source_selectable:
+ primary_aliasizer = ClauseAdapter(secondary).chain(ClauseAdapter(source_selectable, equivalents=self.parent._equivalent_columns))
+
+ secondaryjoin = secondary_aliasizer.traverse(secondaryjoin)
else:
- primaryjoin = ClauseAdapter(source_selectable, include=self.foreign_keys, equivalents=self.parent._equivalent_columns).traverse(primaryjoin)
+ if dest_selectable:
+ primary_aliasizer = ClauseAdapter(dest_selectable, exclude=self.local_side, equivalents=self.mapper._equivalent_columns)
+ if source_selectable:
+ primary_aliasizer.chain(ClauseAdapter(source_selectable, exclude=self.remote_side, equivalents=self.parent._equivalent_columns))
+ elif source_selectable:
+ primary_aliasizer = ClauseAdapter(source_selectable, exclude=self.remote_side, equivalents=self.parent._equivalent_columns)
+
+ secondary_aliasizer = None
- secondaryjoin = self.secondaryjoin
- target_adapter = None
- if dest_selectable:
- if self.direction == ONETOMANY:
- target_adapter = ClauseAdapter(dest_selectable, include=self.foreign_keys, equivalents=self.mapper._equivalent_columns)
- elif self.direction == MANYTOMANY:
- target_adapter = ClauseAdapter(dest_selectable, equivalents=self.mapper._equivalent_columns)
- else:
- target_adapter = ClauseAdapter(dest_selectable, exclude=self.foreign_keys, equivalents=self.mapper._equivalent_columns)
- if secondaryjoin:
- secondaryjoin = target_adapter.traverse(secondaryjoin)
- else:
- primaryjoin = target_adapter.traverse(primaryjoin)
+ primaryjoin = primary_aliasizer.traverse(primaryjoin)
+ target_adapter = secondary_aliasizer or primary_aliasizer
target_adapter.include = target_adapter.exclude = None
-
- return primaryjoin, secondaryjoin, source_selectable or self.parent.local_table, dest_selectable or self.mapper.local_table, target_adapter
+ else:
+ target_adapter = None
+
+ self.__join_cache[key] = ret = (primaryjoin, secondaryjoin, (source_selectable or self.parent.local_table), (dest_selectable or self.mapper.local_table), secondary, target_adapter)
+ return ret
def _get_join(self, parent, primary=True, secondary=True, polymorphic_parent=True):
"""deprecated. use primary_join_against(), secondary_join_against(), full_join_against()"""
- pj, sj, source, dest, adapter = self._create_joins(source_polymorphic=polymorphic_parent)
+ pj, sj, source, dest, secondarytable, adapter = self._create_joins(source_polymorphic=polymorphic_parent)
if primary and secondary:
return pj & sj
@@ -788,7 +822,7 @@ class PropertyLoader(StrategizedProperty):
if not self.viewonly:
self._dependency_processor.register_dependencies(uowcommit)
-PropertyLoader.logger = logging.class_logger(PropertyLoader)
+PropertyLoader.logger = log.class_logger(PropertyLoader)
class BackRef(object):
"""Attached to a PropertyLoader to indicate a complementary reverse relationship.
@@ -799,7 +833,8 @@ class BackRef(object):
self.key = key
self.kwargs = kwargs
self.prop = _prop
-
+ self.extension = attributes.GenericBackrefExtension(self.key)
+
def compile(self, prop):
if self.prop:
return
@@ -817,7 +852,7 @@ class BackRef(object):
relation = PropertyLoader(parent, prop.secondary, pj, sj,
backref=BackRef(prop.key, _prop=prop),
- is_backref=True,
+ _is_backref=True,
**self.kwargs)
mapper._compile_property(self.key, relation);
@@ -826,12 +861,7 @@ class BackRef(object):
mapper._get_property(self.key)._reverse_property = prop
else:
- raise exceptions.ArgumentError("Error creating backref '%s' on relation '%s': property of that name exists on mapper '%s'" % (self.key, prop, mapper))
-
- def get_extension(self):
- """Return an attribute extension to use with this backreference."""
-
- return attributes.GenericBackrefExtension(self.key)
+ raise sa_exc.ArgumentError("Error creating backref '%s' on relation '%s': property of that name exists on mapper '%s'" % (self.key, prop, mapper))
mapper.ColumnProperty = ColumnProperty
mapper.SynonymProperty = SynonymProperty