with_polymorphic selectable, instead of silently
ignoring it. Look for this to become an
exception in 0.7.
+
+ - Another pass through the series of error messages
+ emitted when relationship() is configured with
+ ambiguous arguments. The "foreign_keys"
+ setting is no longer mentioned, as it is almost
+ never needed and it is preferable users set up
+ correct ForeignKey metadata, which is now the
+ recommendation. If 'foreign_keys'
+ is used and is incorrect, the message suggests
+ the attribute is probably unnecessary. Docs
+ for the attribute are beefed up. This
+ because all confused relationship() users on the
+ ML appear to be attempting to use foreign_keys
+ due to the message, which only confuses them
+ further since Table metadata is much clearer.
+
+ - If the "secondary" table has no ForeignKey metadata
+ and no foreign_keys is set, even though the
+ user is passing screwed up information, it is assumed
+ that primary/secondaryjoin expressions should
+ consider only and all cols in "secondary" to be
+ foreign. It's not possible with "secondary" for
+ the foreign keys to be elsewhere in any case.
+ A warning is now emitted instead of an error,
+ and the mapping succeeds. [ticket:1877]
- Moving an o2m object from one collection to
another, or vice versa changing the referenced
addresses_table.c.city=='New York')),
})
-.. _alternate_collection_implementations:
Rows that point to themselves / Mutually Dependent Rows
-------------------------------------------------------
When a structure using the above mapping is flushed, the "widget" row will be INSERTed minus the "favorite_entry_id" value, then all the "entry" rows will be INSERTed referencing the parent "widget" row, and then an UPDATE statement will populate the "favorite_entry_id" column of the "widget" table (it's one row at a time for the time being).
-.. _advdatamapping_entitycollections:
+.. _alternate_collection_implementations:
Alternate Collection Implementations
-------------------------------------
Working with Related Objects
=============================
-Now when we create a ``User``, a blank ``addresses`` collection will be present. Various collection types, such as sets and dictionaries, are possible here (see :ref:`advdatamapping_entitycollections` for details), but by default, the collection is a Python list.
+Now when we create a ``User``, a blank ``addresses`` collection will be present. Various collection types, such as sets and dictionaries, are possible here (see :ref:`alternate_collection_implementations` for details), but by default, the collection is a Python list.
.. sourcecode:: python+sql
-Collection Mapping
-==================
+Advanced Collection Mapping
+===========================
This is an in-depth discussion of collection mechanics. For simple examples, see :ref:`alternate_collection_implementations`.
if isinstance(prop, RelationshipProperty):
for attr in ('argument', 'order_by', 'primaryjoin', 'secondaryjoin',
- 'secondary', '_foreign_keys', 'remote_side'):
+ 'secondary', '_user_defined_foreign_keys', 'remote_side'):
v = getattr(prop, attr)
if isinstance(v, basestring):
setattr(prop, attr, resolve_arg(v))
:param foreign_keys:
a list of columns which are to be used as "foreign key" columns.
- this parameter should be used in conjunction with explicit
- ``primaryjoin`` and ``secondaryjoin`` (if needed) arguments, and
- the columns within the ``foreign_keys`` list should be present
- within those join conditions. Normally, ``relationship()`` will
- inspect the columns within the join conditions to determine
- which columns are the "foreign key" columns, based on
- information in the ``Table`` metadata. Use this argument when no
- ForeignKey's are present in the join condition, or to override
- the table-defined foreign keys.
+ Normally, :func:`relationship` uses the :class:`.ForeignKey`
+ and :class:`.ForeignKeyConstraint` objects present within the
+ mapped or secondary :class:`.Table` to determine the "foreign" side of
+ the join condition. This is used to construct SQL clauses in order
+ to load objects, as well as to "synchronize" values from
+ primary key columns to referencing foreign key columns.
+ The ``foreign_keys`` parameter overrides the notion of what's
+ "foreign" in the table metadata, allowing the specification
+ of a list of :class:`.Column` objects that should be considered
+ part of the foreign key.
+
+ There are only two use cases for ``foreign_keys`` - one, when it is not
+ convenient for :class:`.Table` metadata to contain its own foreign key
+ metadata (which should be almost never, unless reflecting a large amount of
+ tables from a MySQL MyISAM schema, or a schema that doesn't actually
+ have foreign keys on it). The other is for extremely
+ rare and exotic composite foreign key setups where some columns
+ should artificially not be considered as foreign.
:param innerjoin=False:
when ``True``, joined eager loads will use an inner join to join
self.viewonly = viewonly
self.lazy = lazy
self.single_parent = single_parent
- self._foreign_keys = foreign_keys
+ self._user_defined_foreign_keys = foreign_keys
self.collection_class = collection_class
self.passive_deletes = passive_deletes
self.passive_updates = passive_updates
if isinstance(other, (NoneType, expression._Null)):
if self.property.direction == MANYTOONE:
return sql.or_(*[x != None for x in
- self.property._foreign_keys])
+ self.property._calculated_foreign_keys])
else:
return self._criterion_exists()
elif self.property.uselist:
'primaryjoin',
'secondaryjoin',
'secondary',
- '_foreign_keys',
+ '_user_defined_foreign_keys',
'remote_side',
):
if util.callable(getattr(self, attr)):
if self.order_by is not False and self.order_by is not None:
self.order_by = [expression._literal_as_column(x) for x in
util.to_list(self.order_by)]
- self._foreign_keys = \
+ self._user_defined_foreign_keys = \
util.column_set(expression._literal_as_column(x) for x in
- util.to_column_set(self._foreign_keys))
+ util.to_column_set(self._user_defined_foreign_keys))
self.remote_side = \
util.column_set(expression._literal_as_column(x) for x in
util.to_column_set(self.remote_side))
raise sa_exc.ArgumentError("Could not determine join "
"condition between parent/child tables on "
"relationship %s. Specify a 'primaryjoin' "
- "expression. If this is a many-to-many "
- "relationship, 'secondaryjoin' is needed as well."
+ "expression. If 'secondary' is present, "
+ "'secondaryjoin' is needed as well."
% self)
def _col_is_part_of_mappings(self, column):
self.target.c.contains_column(column) or \
self.secondary.c.contains_column(column) is not None
+ def _sync_pairs_from_join(self, join_condition, primary):
+ """Given a join condition, figure out what columns are foreign
+ and are part of a binary "equated" condition to their referecned
+ columns, and convert into a list of tuples of (primary col->foreign col).
+
+ Make several attempts to determine if cols are compared using
+ "=" or other comparators (in which case suggest viewonly),
+ columns are present but not part of the expected mappings, columns
+ don't have any :class:`ForeignKey` information on them, or
+ the ``foreign_keys`` attribute is being used incorrectly.
+
+ """
+ eq_pairs = criterion_as_pairs(join_condition,
+ consider_as_foreign_keys=self._user_defined_foreign_keys,
+ any_operator=self.viewonly)
+
+ eq_pairs = [(l, r) for (l, r) in eq_pairs
+ if self._col_is_part_of_mappings(l)
+ and self._col_is_part_of_mappings(r)
+ or self.viewonly and r in self._user_defined_foreign_keys]
+
+ if not eq_pairs and \
+ self.secondary is not None and \
+ not self._user_defined_foreign_keys:
+ fks = set(self.secondary.c)
+ eq_pairs = criterion_as_pairs(join_condition,
+ consider_as_foreign_keys=fks,
+ any_operator=self.viewonly)
+
+ eq_pairs = [(l, r) for (l, r) in eq_pairs
+ if self._col_is_part_of_mappings(l)
+ and self._col_is_part_of_mappings(r)
+ or self.viewonly and r in fks]
+ if eq_pairs:
+ util.warn("No ForeignKey objects were present "
+ "in secondary table '%s'. Assumed referenced "
+ "foreign key columns %s for join condition '%s' "
+ "on relationship %s" % (
+ self.secondary.description,
+ ", ".join(sorted(["'%s'" % col for col in fks])),
+ join_condition,
+ self
+ ))
+
+ if not eq_pairs:
+ if not self.viewonly and criterion_as_pairs(join_condition,
+ consider_as_foreign_keys=self._user_defined_foreign_keys,
+ any_operator=True):
+ raise sa_exc.ArgumentError("Could not locate any "
+ "equated, locally mapped column pairs for %s "
+ "condition '%s' on relationship %s. For more "
+ "relaxed rules on join conditions, the "
+ "relationship may be marked as viewonly=True."
+ % (
+ primary and 'primaryjoin' or 'secondaryjoin',
+ join_condition,
+ self
+ ))
+ else:
+ if self._user_defined_foreign_keys:
+ raise sa_exc.ArgumentError("Could not determine "
+ "relationship direction for %s condition "
+ "'%s', on relationship %s, using manual "
+ "'foreign_keys' setting. Do the columns "
+ "in 'foreign_keys' represent all, and "
+ "only, the 'foreign' columns in this join "
+ "condition? Does the %s Table already "
+ "have adequate ForeignKey and/or "
+ "ForeignKeyConstraint objects established "
+ "(in which case 'foreign_keys' is usually "
+ "unnecessary)?"
+ % (
+ primary and 'primaryjoin' or 'secondaryjoin',
+ join_condition,
+ self,
+ primary and 'mapped' or 'secondary'
+ ))
+ else:
+ raise sa_exc.ArgumentError("Could not determine "
+ "relationship direction for %s condition "
+ "'%s', on relationship %s. Ensure that the "
+ "referencing Column objects have a "
+ "ForeignKey present, or are otherwise part "
+ "of a ForeignKeyConstraint on their parent "
+ "Table."
+ % (
+ primary and 'primaryjoin' or 'secondaryjoin',
+ join_condition,
+ self
+ ))
+ return eq_pairs
+
def _determine_synchronize_pairs(self):
if self.local_remote_pairs:
- if not self._foreign_keys:
+ if not self._user_defined_foreign_keys:
raise sa_exc.ArgumentError('foreign_keys argument is '
'required with _local_remote_pairs argument')
self.synchronize_pairs = []
for l, r in self.local_remote_pairs:
- if r in self._foreign_keys:
+ if r in self._user_defined_foreign_keys:
self.synchronize_pairs.append((l, r))
- elif l in self._foreign_keys:
+ elif l in self._user_defined_foreign_keys:
self.synchronize_pairs.append((r, l))
else:
- eq_pairs = criterion_as_pairs(self.primaryjoin,
- consider_as_foreign_keys=self._foreign_keys,
- any_operator=self.viewonly)
- eq_pairs = [(l, r) for (l, r) in eq_pairs
- if self._col_is_part_of_mappings(l)
- and self._col_is_part_of_mappings(r)
- or self.viewonly and r in self._foreign_keys]
- if not eq_pairs:
- if not self.viewonly \
- and criterion_as_pairs(self.primaryjoin,
- consider_as_foreign_keys=self._foreign_keys,
- any_operator=True):
- raise sa_exc.ArgumentError("Could not locate any "
- "equated, locally mapped column pairs for "
- "primaryjoin condition '%s' on "
- "relationship %s. For more relaxed rules "
- "on join conditions, the relationship may "
- "be marked as viewonly=True."
- % (self.primaryjoin, self))
- else:
- if self._foreign_keys:
- raise sa_exc.ArgumentError("Could not determine"
- " relationship direction for "
- "primaryjoin condition '%s', on "
- "relationship %s. Do the columns in "
- "'foreign_keys' represent only the "
- "'foreign' columns in this join "
- "condition ?" % (self.primaryjoin,
- self))
- else:
- raise sa_exc.ArgumentError("Could not determine"
- " relationship direction for "
- "primaryjoin condition '%s', on "
- "relationship %s. Specify the "
- "'foreign_keys' argument to indicate "
- "which columns on the relationship are "
- "foreign." % (self.primaryjoin, self))
+ eq_pairs = self._sync_pairs_from_join(self.primaryjoin, True)
self.synchronize_pairs = eq_pairs
if self.secondaryjoin is not None:
- sq_pairs = criterion_as_pairs(self.secondaryjoin,
- consider_as_foreign_keys=self._foreign_keys,
- any_operator=self.viewonly)
- sq_pairs = [(l, r) for (l, r) in sq_pairs
- if self._col_is_part_of_mappings(l)
- and self._col_is_part_of_mappings(r) or r
- in self._foreign_keys]
- if not sq_pairs:
- if not self.viewonly \
- and criterion_as_pairs(self.secondaryjoin,
- consider_as_foreign_keys=self._foreign_keys,
- any_operator=True):
- raise sa_exc.ArgumentError("Could not locate any "
- "equated, locally mapped column pairs for "
- "secondaryjoin condition '%s' on "
- "relationship %s. For more relaxed rules "
- "on join conditions, the relationship may "
- "be marked as viewonly=True."
- % (self.secondaryjoin, self))
- else:
- raise sa_exc.ArgumentError("Could not determine "
- "relationship direction for secondaryjoin "
- "condition '%s', on relationship %s. "
- "Specify the foreign_keys argument to "
- "indicate which columns on the "
- "relationship are foreign."
- % (self.secondaryjoin, self))
+ sq_pairs = self._sync_pairs_from_join(self.secondaryjoin, False)
self.secondary_synchronize_pairs = sq_pairs
else:
self.secondary_synchronize_pairs = None
- self._foreign_keys = util.column_set(r for (l, r) in
+ self._calculated_foreign_keys = util.column_set(r for (l, r) in
self.synchronize_pairs)
if self.secondary_synchronize_pairs:
- self._foreign_keys.update(r for (l, r) in
+ self._calculated_foreign_keys.update(r for (l, r) in
self.secondary_synchronize_pairs)
def _determine_direction(self):
remote = self.remote_side
else:
remote = None
- if not remote or self._foreign_keys.difference(l for (l,
+ if not remote or self._calculated_foreign_keys.difference(l for (l,
r) in self.synchronize_pairs).intersection(remote):
self.direction = ONETOMANY
else:
eq_pairs += self.secondary_synchronize_pairs
else:
eq_pairs = criterion_as_pairs(self.primaryjoin,
- consider_as_foreign_keys=self._foreign_keys,
+ consider_as_foreign_keys=self._calculated_foreign_keys,
any_operator=True)
if self.secondaryjoin is not None:
eq_pairs += \
criterion_as_pairs(self.secondaryjoin,
- consider_as_foreign_keys=self._foreign_keys,
+ consider_as_foreign_keys=self._calculated_foreign_keys,
any_operator=True)
eq_pairs = [(l, r) for (l, r) in eq_pairs
if self._col_is_part_of_mappings(l)
"a non-secondary relationship."
)
foreign_keys = kwargs.pop('foreign_keys',
- self._foreign_keys)
+ self._user_defined_foreign_keys)
parent = self.parent.primary_mapper()
kwargs.setdefault('viewonly', self.viewonly)
kwargs.setdefault('post_update', self.post_update)
assert False, "Callable did not raise an exception"
except except_cls, e:
assert re.search(msg, str(e)), "%r !~ %s" % (msg, e)
+ print str(e)
def fail(msg):
assert False, msg
Column('id', Integer, primary_key=True),
Column('fid', Integer))
+ Table('foos_with_fks', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('fid', Integer, ForeignKey('foos_with_fks.id')))
+ Table('bars_with_fks', metadata,
+ Column('id', Integer, primary_key=True),
+ Column('fid', Integer, ForeignKey('foos_with_fks.id')))
+
@classmethod
def setup_classes(cls):
class Foo(_base.Entity):
foreign_keys=[foos.c.id, bars.c.fid])})
mapper(Bar, bars)
- assert_raises_message(
- sa.exc.ArgumentError,
- "Do the columns in 'foreign_keys' represent only the "
- "'foreign' columns in this join condition ?",
- sa.orm.compile_mappers)
+ assert_raises_message(sa.exc.ArgumentError,
+ "Could not determine relationship "
+ "direction for primaryjoin condition "
+ "'foos.id = bars.fid', on relationship "
+ "Foo.bars, using manual 'foreign_keys' "
+ "setting. Do the columns in "
+ "'foreign_keys' represent all, and only, "
+ "the 'foreign' columns in this join "
+ r"condition\? Does the mapped Table "
+ "already have adequate ForeignKey and/or "
+ "ForeignKeyConstraint objects "
+ r"established \(in which case "
+ r"'foreign_keys' is usually unnecessary\)\?"
+ , sa.orm.compile_mappers)
@testing.resolve_artifact_names
def test_ambiguous_remoteside_o2m(self):
viewonly=True)})
mapper(Bar, bars)
- assert_raises_message(
- sa.exc.ArgumentError,
- "Could not determine relationship direction for primaryjoin condition",
- sa.orm.compile_mappers)
+ assert_raises_message(sa.exc.ArgumentError,
+ 'Could not determine relationship '
+ 'direction for primaryjoin condition',
+ sa.orm.compile_mappers)
+ sa.orm.clear_mappers()
+ mapper(Foo, foos_with_fks, properties={
+ 'bars':relationship(Bar,
+ primaryjoin=foos_with_fks.c.id>bars_with_fks.c.fid,
+ viewonly=True)})
+ mapper(Bar, bars_with_fks)
+ sa.orm.compile_mappers()
+
@testing.resolve_artifact_names
def test_no_equated_self_ref_viewonly(self):
mapper(Foo, foos, properties={
viewonly=True)})
mapper(Bar, bars)
- assert_raises_message(
- sa.exc.ArgumentError,
- "Specify the 'foreign_keys' argument to indicate which columns "
- "on the relationship are foreign.", sa.orm.compile_mappers)
+ assert_raises_message(sa.exc.ArgumentError,
+ "Could not determine relationship "
+ "direction for primaryjoin condition "
+ "'foos.id > foos.fid', on relationship "
+ "Foo.foos. Ensure that the referencing "
+ "Column objects have a ForeignKey "
+ "present, or are otherwise part of a "
+ "ForeignKeyConstraint on their parent "
+ "Table.", sa.orm.compile_mappers)
+
+ sa.orm.clear_mappers()
+ mapper(Foo, foos_with_fks, properties={
+ 'foos':relationship(Foo,
+ primaryjoin=foos_with_fks.c.id>foos_with_fks.c.fid,
+ viewonly=True)})
+ mapper(Bar, bars_with_fks)
+ sa.orm.compile_mappers()
@testing.resolve_artifact_names
def test_no_equated_self_ref_viewonly_fks(self):
"Could not determine relationship direction for primaryjoin condition",
sa.orm.compile_mappers)
+ sa.orm.clear_mappers()
+ mapper(Foo, foos_with_fks, properties={
+ 'bars':relationship(Bar,
+ primaryjoin=foos_with_fks.c.id==bars_with_fks.c.fid)})
+ mapper(Bar, bars_with_fks)
+ sa.orm.compile_mappers()
+
@testing.resolve_artifact_names
def test_equated_self_ref(self):
mapper(Foo, foos, properties={
sa.exc.ArgumentError,
"Could not determine relationship direction for primaryjoin condition",
sa.orm.compile_mappers)
+
@testing.resolve_artifact_names
def test_equated_self_ref_wrong_fks(self):
Table('bars', metadata,
Column('id', Integer, primary_key=True))
+ Table('foobars_with_fks', metadata,
+ Column('fid', Integer, ForeignKey('foos.id')),
+ Column('bid', Integer, ForeignKey('bars.id'))
+ )
+
+ Table('foobars_with_many_columns', metadata,
+ Column('fid', Integer),
+ Column('bid', Integer),
+ Column('fid1', Integer),
+ Column('bid1', Integer),
+ Column('fid2', Integer),
+ Column('bid2', Integer),
+ )
+
@classmethod
@testing.resolve_artifact_names
def setup_classes(cls):
"on relationship",
sa.orm.compile_mappers)
+ @testing.resolve_artifact_names
+ def test_no_fks_warning_1(self):
+ mapper(Foo, foos, properties={
+ 'bars': relationship(Bar, secondary=foobars,
+ primaryjoin=foos.c.id==foobars.c.fid,
+ secondaryjoin=foobars.c.bid==bars.c.id)})
+ mapper(Bar, bars)
+
+ assert_raises_message(sa.exc.SAWarning,
+ "No ForeignKey objects were present in "
+ "secondary table 'foobars'. Assumed "
+ "referenced foreign key columns "
+ "'foobars.bid', 'foobars.fid' for join "
+ "condition 'foos.id = foobars.fid' on "
+ "relationship Foo.bars",
+ sa.orm.compile_mappers)
+
+ sa.orm.clear_mappers()
+ mapper(Foo, foos, properties={
+ 'bars': relationship(Bar, secondary=foobars_with_many_columns,
+ primaryjoin=foos.c.id==foobars_with_many_columns.c.fid,
+ secondaryjoin=foobars_with_many_columns.c.bid==bars.c.id)})
+ mapper(Bar, bars)
+
+ assert_raises_message(sa.exc.SAWarning,
+ "No ForeignKey objects were present in "
+ "secondary table 'foobars_with_many_colum"
+ "ns'. Assumed referenced foreign key "
+ "columns 'foobars_with_many_columns.bid',"
+ " 'foobars_with_many_columns.bid1', "
+ "'foobars_with_many_columns.bid2', "
+ "'foobars_with_many_columns.fid', "
+ "'foobars_with_many_columns.fid1', "
+ "'foobars_with_many_columns.fid2' for "
+ "join condition 'foos.id = "
+ "foobars_with_many_columns.fid' on "
+ "relationship Foo.bars",
+ sa.orm.compile_mappers)
+
+ @testing.emits_warning(r'No ForeignKey objects.*')
+ @testing.resolve_artifact_names
+ def test_no_fks_warning_2(self):
+ mapper(Foo, foos, properties={
+ 'bars': relationship(Bar, secondary=foobars,
+ primaryjoin=foos.c.id==foobars.c.fid,
+ secondaryjoin=foobars.c.bid==bars.c.id)})
+ mapper(Bar, bars)
+ sa.orm.compile_mappers()
+ eq_(
+ Foo.bars.property.synchronize_pairs,
+ [(foos.c.id, foobars.c.fid)]
+ )
+ eq_(
+ Foo.bars.property.secondary_synchronize_pairs,
+ [(bars.c.id, foobars.c.bid)]
+ )
+
+ sa.orm.clear_mappers()
+ mapper(Foo, foos, properties={
+ 'bars': relationship(Bar, secondary=foobars_with_many_columns,
+ primaryjoin=foos.c.id==foobars_with_many_columns.c.fid,
+ secondaryjoin=foobars_with_many_columns.c.bid==bars.c.id)})
+ mapper(Bar, bars)
+ sa.orm.compile_mappers()
+ eq_(
+ Foo.bars.property.synchronize_pairs,
+ [(foos.c.id, foobars_with_many_columns.c.fid)]
+ )
+ eq_(
+ Foo.bars.property.secondary_synchronize_pairs,
+ [(bars.c.id, foobars_with_many_columns.c.bid)]
+ )
+
+
@testing.resolve_artifact_names
def test_bad_primaryjoin(self):
mapper(Foo, foos, properties={
sa.exc.ArgumentError,
"Could not determine relationship direction for primaryjoin condition",
sa.orm.compile_mappers)
+
+ sa.orm.clear_mappers()
+ mapper(Foo, foos, properties={
+ 'bars': relationship(Bar,
+ secondary=foobars_with_fks,
+ primaryjoin=foos.c.id > foobars_with_fks.c.fid,
+ secondaryjoin=foobars_with_fks.c.bid<=bars.c.id)})
+ mapper(Bar, bars)
+ assert_raises_message(
+ sa.exc.ArgumentError,
+ "Could not locate any equated, locally mapped column pairs for primaryjoin condition ",
+ sa.orm.compile_mappers)
+ sa.orm.clear_mappers()
+ mapper(Foo, foos, properties={
+ 'bars': relationship(Bar,
+ secondary=foobars_with_fks,
+ primaryjoin=foos.c.id > foobars_with_fks.c.fid,
+ secondaryjoin=foobars_with_fks.c.bid<=bars.c.id,
+ viewonly=True)})
+ mapper(Bar, bars)
+ sa.orm.compile_mappers()
+
@testing.resolve_artifact_names
def test_bad_secondaryjoin(self):
mapper(Foo, foos, properties={
foreign_keys=[foobars.c.fid])})
mapper(Bar, bars)
- assert_raises_message(
- sa.exc.ArgumentError,
- "Could not determine relationship direction for secondaryjoin "
- "condition", sa.orm.compile_mappers)
+ assert_raises_message(sa.exc.ArgumentError,
+ "Could not determine relationship "
+ "direction for secondaryjoin condition "
+ r"'foobars.bid \<\= bars.id', on "
+ "relationship Foo.bars, using manual "
+ "'foreign_keys' setting. Do the columns "
+ "in 'foreign_keys' represent all, and only, the "
+ "'foreign' columns in this join "
+ r"condition\? Does the "
+ "secondary Table already have adequate "
+ "ForeignKey and/or ForeignKeyConstraint "
+ r"objects established \(in which case "
+ r"'foreign_keys' is usually unnecessary\)?"
+ , sa.orm.compile_mappers)
@testing.resolve_artifact_names
def test_no_equated_secondaryjoin(self):