From: Mike Bayer Date: Fri, 25 Jun 2021 20:51:50 +0000 (-0400) Subject: have automap suppress overlaps warning for mapped secondary X-Git-Tag: rel_1_4_20~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=65e16e9b3e9ceabfbf3f11fbefa498109d8b335a;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git have automap suppress overlaps warning for mapped secondary Fixed regression in :mod:`sqlalchemy.ext.automap` extension such that the use case of creating an explicit mapped class to a table that is also the :paramref:`_orm.relationship.secondary` element of a :func:`_orm.relationship` that automap will be generating would emit the "overlaps" warnings introduced in 1.4 and discussed at :ref:`error_qzyx`. While generating this case from automap is still subject to the same caveats that the "overlaps" warning refers towards, as automap is intended for more ad-hoc use cases, the condition which produces the warning is disabled when a many-to-many relationship with this particular pattern is generated. Fixes: #6679 Change-Id: Ib3a53982b076ed4999b0d3235f84008b9e2f1cce --- diff --git a/doc/build/changelog/unreleased_14/6679.rst b/doc/build/changelog/unreleased_14/6679.rst new file mode 100644 index 0000000000..ca4cd7b494 --- /dev/null +++ b/doc/build/changelog/unreleased_14/6679.rst @@ -0,0 +1,16 @@ +.. change:: + :tags: bug, regression, ext + :tickets: 6679 + + Fixed regression in :mod:`sqlalchemy.ext.automap` extension such that the + use case of creating an explicit mapped class to a table that is also the + :paramref:`_orm.relationship.secondary` element of a + :func:`_orm.relationship` that automap will be generating would emit the + "overlaps" warnings introduced in 1.4 and discussed at :ref:`error_qzyx`. + While generating this case from automap is still subject to the same + caveats that the "overlaps" warning refers towards, as automap is intended + for more ad-hoc use cases, the condition which produces the warning is + disabled when a many-to-many relationship with this particular pattern is + generated. + + diff --git a/lib/sqlalchemy/ext/automap.py b/lib/sqlalchemy/ext/automap.py index e20435911b..8b75dce7b1 100644 --- a/lib/sqlalchemy/ext/automap.py +++ b/lib/sqlalchemy/ext/automap.py @@ -1153,6 +1153,11 @@ def _m2m_relationship( create_backref = backref_name not in referred_cfg.properties + if table in table_to_map_config: + overlaps = "__*" + else: + overlaps = None + if relationship_name not in map_config.properties: if create_backref: backref_obj = generate_relationship( @@ -1163,9 +1168,11 @@ def _m2m_relationship( referred_cls, local_cls, collection_class=collection_class, + overlaps=overlaps, ) else: backref_obj = None + rel = generate_relationship( automap_base, interfaces.MANYTOMANY, @@ -1173,6 +1180,7 @@ def _m2m_relationship( relationship_name, local_cls, referred_cls, + overlaps=overlaps, secondary=table, primaryjoin=and_( fk.column == fk.parent for fk in m2m_const[0].elements @@ -1198,6 +1206,7 @@ def _m2m_relationship( backref_name, referred_cls, local_cls, + overlaps=overlaps, secondary=table, primaryjoin=and_( fk.column == fk.parent for fk in m2m_const[1].elements diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 7882eff70d..2224b4902b 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -3427,6 +3427,14 @@ class JoinCondition(object): and pr not in self.prop._reverse_property and pr.key not in self.prop._overlaps and self.prop.key not in pr._overlaps + # note: the "__*" symbol is used internally by + # SQLAlchemy as a general means of supressing the + # overlaps warning for some extension cases, however + # this is not currently + # a publicly supported symbol and may change at + # any time. + and "__*" not in self.prop._overlaps + and "__*" not in pr._overlaps and not self.prop.parent.is_sibling(pr.parent) and not self.prop.mapper.is_sibling(pr.mapper) and not self.prop.parent.is_sibling(pr.mapper) diff --git a/test/ext/test_automap.py b/test/ext/test_automap.py index 392941900d..eaafa34778 100644 --- a/test/ext/test_automap.py +++ b/test/ext/test_automap.py @@ -6,6 +6,7 @@ from sqlalchemy import create_engine from sqlalchemy import ForeignKey from sqlalchemy import Integer from sqlalchemy import MetaData +from sqlalchemy import select from sqlalchemy import String from sqlalchemy import testing from sqlalchemy.ext.automap import automap_base @@ -17,6 +18,7 @@ from sqlalchemy.orm import relationship from sqlalchemy.orm import Session from sqlalchemy.testing import assert_raises_message from sqlalchemy.testing import fixtures +from sqlalchemy.testing import is_ from sqlalchemy.testing.mock import Mock from sqlalchemy.testing.mock import patch from sqlalchemy.testing.schema import Column @@ -230,6 +232,45 @@ class AutomapTest(fixtures.MappedTest): assert isinstance(i1.order_collection, list) assert o1 in i1.order_collection + def test_m2m_relationship_also_map_the_secondary(self): + """test #6679""" + + Base = automap_base(metadata=self.tables_test_metadata) + + # extend the table to have pk cols + Table( + "order_items", + self.tables_test_metadata, + Column("item_id", None, ForeignKey("items.id"), primary_key=True), + Column( + "order_id", None, ForeignKey("orders.id"), primary_key=True + ), + extend_existing=True, + ) + + # then also map to it + class OrderItem(Base): + __tablename__ = "order_items" + + Base.prepare() + + Order = Base.classes["orders"] + Item = Base.classes["items"] + + o1 = Order() + i1 = Item(description="x") + o1.items_collection.append(i1) + + s = fixtures.fixture_session() + + s.add(o1) + s.flush() + + oi = s.execute(select(OrderItem)).scalars().one() + + is_(oi.items, i1) + is_(oi.orders, o1) + def test_relationship_pass_params(self): Base = automap_base(metadata=self.tables_test_metadata)