From: Mike Bayer Date: Tue, 2 Jan 2024 16:36:20 +0000 (-0500) Subject: force uselist=False for all collection class not present X-Git-Tag: rel_2_0_25~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2ce6010c924e963dcd9245b99e583611be5fb061;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git force uselist=False for all collection class not present Fixed issue where ORM Annotated Declarative would mis-interpret the left hand side of a relationship without any collection specified as uselist=True if the left type were given as a class and not a string, without using future-style annotations. Fixes: #10815 Change-Id: I85daccec03f7e6ea3b49eb07c06e0f85e361a1c0 (cherry picked from commit c1139c2e5d2f14738798d3c0deb876286014c808) --- diff --git a/doc/build/changelog/unreleased_20/10815.rst b/doc/build/changelog/unreleased_20/10815.rst new file mode 100644 index 0000000000..2240764aeb --- /dev/null +++ b/doc/build/changelog/unreleased_20/10815.rst @@ -0,0 +1,8 @@ +.. change:: + :tags: bug, orm + :tickets: 10815 + + Fixed issue where ORM Annotated Declarative would mis-interpret the left + hand side of a relationship without any collection specified as + uselist=True if the left type were given as a class and not a string, + without using future-style annotations. diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index b19d23b277..bcdd79cb75 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -1814,15 +1814,12 @@ class RelationshipProperty( argument, originating_module ) - # we don't allow the collection class to be a - # __forward_arg__ right now, so if we see a forward arg here, - # we know there was no collection class either - if ( - self.collection_class is None - and not is_write_only - and not is_dynamic - ): - self.uselist = False + if ( + self.collection_class is None + and not is_write_only + and not is_dynamic + ): + self.uselist = False # ticket #8759 # if a lead argument was given to relationship(), like diff --git a/test/orm/declarative/test_tm_future_annotations_sync.py b/test/orm/declarative/test_tm_future_annotations_sync.py index e64834b39d..e2a442767a 100644 --- a/test/orm/declarative/test_tm_future_annotations_sync.py +++ b/test/orm/declarative/test_tm_future_annotations_sync.py @@ -2750,7 +2750,7 @@ class RelationshipLHSTest(fixtures.TestBase, testing.AssertsCompiledSQL): is_false(B.__mapper__.attrs["a"].uselist) is_false(B.__mapper__.attrs["a_warg"].uselist) - def test_one_to_one_example(self, decl_base: Type[DeclarativeBase]): + def test_one_to_one_example_quoted(self, decl_base: Type[DeclarativeBase]): """test example in the relationship docs will derive uselist=False correctly""" @@ -2774,6 +2774,32 @@ class RelationshipLHSTest(fixtures.TestBase, testing.AssertsCompiledSQL): is_(p1.child, c1) is_(c1.parent, p1) + def test_one_to_one_example_non_quoted( + self, decl_base: Type[DeclarativeBase] + ): + """test example in the relationship docs will derive uselist=False + correctly""" + + class Child(decl_base): + __tablename__ = "child" + + id: Mapped[int] = mapped_column(primary_key=True) + parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id")) + parent: Mapped["Parent"] = relationship(back_populates="child") + + class Parent(decl_base): + __tablename__ = "parent" + + id: Mapped[int] = mapped_column(primary_key=True) + child: Mapped[Child] = relationship( # noqa: F821 + back_populates="parent" + ) + + c1 = Child() + p1 = Parent(child=c1) + is_(p1.child, c1) + is_(c1.parent, p1) + def test_collection_class_dict_no_collection(self, decl_base): class A(decl_base): __tablename__ = "a" diff --git a/test/orm/declarative/test_typed_mapping.py b/test/orm/declarative/test_typed_mapping.py index 44327324ca..0f1bb452d5 100644 --- a/test/orm/declarative/test_typed_mapping.py +++ b/test/orm/declarative/test_typed_mapping.py @@ -2741,7 +2741,7 @@ class RelationshipLHSTest(fixtures.TestBase, testing.AssertsCompiledSQL): is_false(B.__mapper__.attrs["a"].uselist) is_false(B.__mapper__.attrs["a_warg"].uselist) - def test_one_to_one_example(self, decl_base: Type[DeclarativeBase]): + def test_one_to_one_example_quoted(self, decl_base: Type[DeclarativeBase]): """test example in the relationship docs will derive uselist=False correctly""" @@ -2765,6 +2765,32 @@ class RelationshipLHSTest(fixtures.TestBase, testing.AssertsCompiledSQL): is_(p1.child, c1) is_(c1.parent, p1) + def test_one_to_one_example_non_quoted( + self, decl_base: Type[DeclarativeBase] + ): + """test example in the relationship docs will derive uselist=False + correctly""" + + class Child(decl_base): + __tablename__ = "child" + + id: Mapped[int] = mapped_column(primary_key=True) + parent_id: Mapped[int] = mapped_column(ForeignKey("parent.id")) + parent: Mapped["Parent"] = relationship(back_populates="child") + + class Parent(decl_base): + __tablename__ = "parent" + + id: Mapped[int] = mapped_column(primary_key=True) + child: Mapped[Child] = relationship( # noqa: F821 + back_populates="parent" + ) + + c1 = Child() + p1 = Parent(child=c1) + is_(p1.child, c1) + is_(c1.parent, p1) + def test_collection_class_dict_no_collection(self, decl_base): class A(decl_base): __tablename__ = "a"