]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
have automap suppress overlaps warning for mapped secondary
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 25 Jun 2021 20:51:50 +0000 (16:51 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 25 Jun 2021 20:53:01 +0000 (16:53 -0400)
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

doc/build/changelog/unreleased_14/6679.rst [new file with mode: 0644]
lib/sqlalchemy/ext/automap.py
lib/sqlalchemy/orm/relationships.py
test/ext/test_automap.py

diff --git a/doc/build/changelog/unreleased_14/6679.rst b/doc/build/changelog/unreleased_14/6679.rst
new file mode 100644 (file)
index 0000000..ca4cd7b
--- /dev/null
@@ -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.
+
+
index e20435911b68628f8a0ff64edcf009746daad3b4..8b75dce7b13c6f72a85fbbef0baa1210c51f6099 100644 (file)
@@ -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
index 7882eff70df54cb9475857b4a61aa9b2d15cb623..2224b4902bcc7dbd74d03a16ec16a9bd0a4b961f 100644 (file)
@@ -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)
index 392941900db655d8b94f410d78bb665bd19d9e96..eaafa3477858ea47ce6e170bf9863de8e903b24c 100644 (file)
@@ -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)