diff options
| -rwxr-xr-x | lib/sqlalchemy/ext/declarative.py | 6 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/relationships.py | 130 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 49 | ||||
| -rw-r--r-- | test/ext/test_declarative.py | 22 |
4 files changed, 96 insertions, 111 deletions
diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index 891130a48..7e5ae03d9 100755 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -1392,6 +1392,10 @@ class _GetTable(object): def _deferred_relationship(cls, prop): def resolve_arg(arg): import sqlalchemy + from sqlalchemy.orm import foreign, remote + + fallback = sqlalchemy.__dict__.copy() + fallback.update({'foreign':foreign, 'remote':remote}) def access_cls(key): if key in cls._decl_class_registry: @@ -1401,7 +1405,7 @@ def _deferred_relationship(cls, prop): elif key in cls.metadata._schemas: return _GetTable(key, cls.metadata) else: - return sqlalchemy.__dict__[key] + return fallback[key] d = util.PopulateDict(access_cls) def return_cls(): diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index adf3b0cde..c1503a79a 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -163,38 +163,42 @@ class JoinCondition(object): "condition between parent/child tables on " "relationship %s - there are no foreign keys " "linking these tables via secondary table '%s'. " - "Ensure that referencing columns are associated with a "\ - "ForeignKey or ForeignKeyConstraint, or specify 'primaryjoin' "\ - "and 'secondaryjoin' expressions." + "Ensure that referencing columns are associated " + "with a ForeignKey or ForeignKeyConstraint, or " + "specify 'primaryjoin' and 'secondaryjoin' " + "expressions." % (self.prop, self.secondary)) else: raise sa_exc.NoForeignKeysError("Could not determine join " "condition between parent/child tables on " "relationship %s - there are no foreign keys " "linking these tables. " - "Ensure that referencing columns are associated with a " - "ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' " - "expression." + "Ensure that referencing columns are associated " + "with a ForeignKey or ForeignKeyConstraint, or " + "specify a 'primaryjoin' expression." % self.prop) except sa_exc.AmbiguousForeignKeysError, afke: if self.secondary is not None: - raise sa_exc.AmbiguousForeignKeysError("Could not determine join " + raise sa_exc.AmbiguousForeignKeysError( + "Could not determine join " "condition between parent/child tables on " "relationship %s - there are multiple foreign key " "paths linking the tables via secondary table '%s'. " "Specify the 'foreign_keys' " "argument, providing a list of those columns which " - "should be counted as containing a foreign key reference " - "from the secondary table to each of the parent and child tables." + "should be counted as containing a foreign key " + "reference from the secondary table to each of the " + "parent and child tables." % (self.prop, self.secondary)) else: - raise sa_exc.AmbiguousForeignKeysError("Could not determine join " + raise sa_exc.AmbiguousForeignKeysError( + "Could not determine join " "condition between parent/child tables on " "relationship %s - there are multiple foreign key " - "paths linking the tables. Specify the 'foreign_keys' " - "argument, providing a list of those columns which " - "should be counted as containing a foreign key reference " - "to the parent table." + "paths linking the tables. Specify the " + "'foreign_keys' argument, providing a list of those " + "columns which should be counted as containing a " + "foreign key reference to the parent table." % self.prop) @util.memoized_property @@ -690,13 +694,13 @@ class JoinCondition(object): def _gather_join_annotations(self, annotation): s = set( - self._gather_columns_with_annotation(self.primaryjoin, - annotation) + self._gather_columns_with_annotation( + self.primaryjoin, annotation) ) if self.secondaryjoin is not None: s.update( - self._gather_columns_with_annotation(self.secondaryjoin, - annotation) + self._gather_columns_with_annotation( + self.secondaryjoin, annotation) ) return s @@ -735,8 +739,8 @@ class JoinCondition(object): # adjust the join condition for single table inheritance, # in the case that the join is to a subclass - # this is analogous to the "_adjust_for_single_table_inheritance()" - # method in Query. + # this is analogous to the + # "_adjust_for_single_table_inheritance()" method in Query. if single_crit is not None: if secondaryjoin is not None: @@ -778,51 +782,49 @@ class JoinCondition(object): return primaryjoin, secondaryjoin, secondary, \ target_adapter, dest_selectable + def create_lazy_clause(self, reverse_direction=False): + binds = util.column_dict() + lookup = util.column_dict() + equated_columns = util.column_dict() + + if reverse_direction and self.secondaryjoin is None: + for l, r in self.local_remote_pairs: + _list = lookup.setdefault(r, []) + _list.append((r, l)) + equated_columns[l] = r + else: + for l, r in self.local_remote_pairs: + _list = lookup.setdefault(l, []) + _list.append((l, r)) + equated_columns[r] = l + + def col_to_bind(col): + if col in lookup: + for tobind, equated in lookup[col]: + if equated in binds: + return None + if col not in binds: + binds[col] = sql.bindparam( + None, None, type_=col.type, unique=True) + return binds[col] + return None + + lazywhere = self.primaryjoin + + if self.secondaryjoin is None or not reverse_direction: + lazywhere = visitors.replacement_traverse( + lazywhere, {}, col_to_bind) + + if self.secondaryjoin is not None: + secondaryjoin = self.secondaryjoin + if reverse_direction: + secondaryjoin = visitors.replacement_traverse( + secondaryjoin, {}, col_to_bind) + lazywhere = sql.and_(lazywhere, secondaryjoin) + + bind_to_col = dict((binds[col].key, col) for col in binds) -################# everything below is TODO ################################ - -def _create_lazy_clause(cls, prop, reverse_direction=False): - binds = util.column_dict() - lookup = util.column_dict() - equated_columns = util.column_dict() - - if reverse_direction and prop.secondaryjoin is None: - for l, r in prop.local_remote_pairs: - _list = lookup.setdefault(r, []) - _list.append((r, l)) - equated_columns[l] = r - else: - for l, r in prop.local_remote_pairs: - _list = lookup.setdefault(l, []) - _list.append((l, r)) - equated_columns[r] = l - - def col_to_bind(col): - if col in lookup: - for tobind, equated in lookup[col]: - if equated in binds: - return None - if col not in binds: - binds[col] = sql.bindparam(None, None, type_=col.type, unique=True) - return binds[col] - return None - - lazywhere = prop.primaryjoin - - if prop.secondaryjoin is None or not reverse_direction: - lazywhere = visitors.replacement_traverse( - lazywhere, {}, col_to_bind) - - if prop.secondaryjoin is not None: - secondaryjoin = prop.secondaryjoin - if reverse_direction: - secondaryjoin = visitors.replacement_traverse( - secondaryjoin, {}, col_to_bind) - lazywhere = sql.and_(lazywhere, secondaryjoin) - - bind_to_col = dict((binds[col].key, col) for col in binds) - - return lazywhere, bind_to_col, equated_columns + return lazywhere, bind_to_col, equated_columns diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 00739ea03..a4cdfba1a 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -324,14 +324,14 @@ class LazyLoader(AbstractRelationshipLoader): def init(self): super(LazyLoader, self).init() + join_condition = self.parent_property._join_condition self._lazywhere, \ self._bind_to_col, \ - self._equated_columns = self._create_lazy_clause(self.parent_property) + self._equated_columns = join_condition.create_lazy_clause() self._rev_lazywhere, \ self._rev_bind_to_col, \ - self._rev_equated_columns = self._create_lazy_clause( - self.parent_property, + self._rev_equated_columns = join_condition.create_lazy_clause( reverse_direction=True) self.logger.info("%s lazy loading clause %s", self, self._lazywhere) @@ -617,49 +617,6 @@ class LazyLoader(AbstractRelationshipLoader): return reset_for_lazy_callable, None, None - @classmethod - def _create_lazy_clause(cls, prop, reverse_direction=False): - binds = util.column_dict() - lookup = util.column_dict() - equated_columns = util.column_dict() - - if reverse_direction and prop.secondaryjoin is None: - for l, r in prop.local_remote_pairs: - _list = lookup.setdefault(r, []) - _list.append((r, l)) - equated_columns[l] = r - else: - for l, r in prop.local_remote_pairs: - _list = lookup.setdefault(l, []) - _list.append((l, r)) - equated_columns[r] = l - - def col_to_bind(col): - if col in lookup: - for tobind, equated in lookup[col]: - if equated in binds: - return None - if col not in binds: - binds[col] = sql.bindparam(None, None, type_=col.type, unique=True) - return binds[col] - return None - - lazywhere = prop.primaryjoin - - if prop.secondaryjoin is None or not reverse_direction: - lazywhere = visitors.replacement_traverse( - lazywhere, {}, col_to_bind) - - if prop.secondaryjoin is not None: - secondaryjoin = prop.secondaryjoin - if reverse_direction: - secondaryjoin = visitors.replacement_traverse( - secondaryjoin, {}, col_to_bind) - lazywhere = sql.and_(lazywhere, secondaryjoin) - - bind_to_col = dict((binds[col].key, col) for col in binds) - - return lazywhere, bind_to_col, equated_columns log.class_logger(LazyLoader) diff --git a/test/ext/test_declarative.py b/test/ext/test_declarative.py index 69042b5c8..5e185f664 100644 --- a/test/ext/test_declarative.py +++ b/test/ext/test_declarative.py @@ -368,6 +368,28 @@ class DeclarativeTest(DeclarativeTestBase): assert class_mapper(User).get_property('props').secondary \ is user_to_prop + def test_string_dependency_resolution_annotations(self): + Base = decl.declarative_base() + + class Parent(Base): + __tablename__ = 'parent' + id = Column(Integer, primary_key=True) + name = Column(String) + children = relationship("Child", + primaryjoin="Parent.name==remote(foreign(func.lower(Child.name_upper)))" + ) + + class Child(Base): + __tablename__ = 'child' + id = Column(Integer, primary_key=True) + name_upper = Column(String) + + configure_mappers() + eq_( + Parent.children.property._calculated_foreign_keys, + set([Child.name_upper.property.columns[0]]) + ) + def test_shared_class_registry(self): reg = {} Base1 = decl.declarative_base(testing.db, class_registry=reg) |
