diff options
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 6 | ||||
-rw-r--r-- | lib/sqlalchemy/sql/util.py | 16 | ||||
-rw-r--r-- | test/ext/declarative.py | 16 |
4 files changed, 38 insertions, 5 deletions
@@ -112,6 +112,11 @@ CHANGES - The `echo_uow` flag on `Session` is deprecated, and unit-of-work logging is now application-level only, not per-session level. + +- declarative + - Fixed bug whereby mapper couldn't initialize if a composite + primary key referenced another table that was not defined + yet [ticket:1161] - schema - Added "sorted_tables" accessor to MetaData, which returns diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index cfea61e26..d6dbc2634 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -545,11 +545,11 @@ class Mapper(object): # determine primary key from argument or mapped_table pks - reduce to the minimal set of columns if self.primary_key_argument: primary_key = sqlutil.reduce_columns( - self.mapped_table.corresponding_column(c) - for c in self.primary_key_argument) + [self.mapped_table.corresponding_column(c) for c in self.primary_key_argument], + ignore_nonexistent_tables=True) else: primary_key = sqlutil.reduce_columns( - self._pks_by_table[self.mapped_table]) + self._pks_by_table[self.mapped_table], ignore_nonexistent_tables=True) if len(primary_key) == 0: raise sa_exc.ArgumentError("Mapper %s could not assemble any primary key columns for mapped table '%s'" % (self, self.mapped_table.description)) diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py index e1636ccf9..d9c3ed899 100644 --- a/lib/sqlalchemy/sql/util.py +++ b/lib/sqlalchemy/sql/util.py @@ -190,7 +190,7 @@ def splice_joins(left, right, stop_on=None): return ret -def reduce_columns(columns, *clauses): +def reduce_columns(columns, *clauses, **kw): """given a list of columns, return a 'reduced' set based on natural equivalents. the set is reduced to the smallest list of columns which have no natural @@ -200,12 +200,17 @@ def reduce_columns(columns, *clauses): \*clauses is an optional list of join clauses which will be traversed to further identify columns that are "equivalent". + \**kw may specify 'ignore_nonexistent_tables' to ignore foreign keys + whose tables are not yet configured. + This function is primarily used to determine the most minimal "primary key" from a selectable, by reducing the set of primary key columns present in the the selectable to just those that are not repeated. """ + ignore_nonexistent_tables = kw.pop('ignore_nonexistent_tables', False) + columns = util.OrderedSet(columns) omit = set() @@ -214,7 +219,14 @@ def reduce_columns(columns, *clauses): for c in columns: if c is col: continue - if fk.column.shares_lineage(c): + try: + fk_col = fk.column + except exc.NoReferencedTableError: + if ignore_nonexistent_tables: + continue + else: + raise + if fk_col.shares_lineage(c): omit.add(col) break diff --git a/test/ext/declarative.py b/test/ext/declarative.py index 6b226cc9f..3121f959f 100644 --- a/test/ext/declarative.py +++ b/test/ext/declarative.py @@ -679,6 +679,22 @@ class DeclarativeTest(testing.TestBase, testing.AssertsExecutionResults): Address(email='one'), Address(email='two')])]) + def test_pk_with_fk_init(self): + class Bar(Base): + __tablename__ = 'bar' + + id = sa.Column(sa.Integer, sa.ForeignKey("foo.id"), primary_key=True) + ex = sa.Column(sa.Integer, primary_key=True) + + class Foo(Base): + __tablename__ = 'foo' + + id = sa.Column(sa.Integer, primary_key=True) + bars = sa.orm.relation(Bar) + + assert Bar.__mapper__.primary_key[0] is Bar.__table__.c.id + assert Bar.__mapper__.primary_key[1] is Bar.__table__.c.ex + def test_single_inheritance(self): class Company(Base, ComparableEntity): __tablename__ = 'companies' |