From: Mike Bayer Date: Wed, 2 Mar 2022 16:51:57 +0000 (-0500) Subject: prevent Mapped[] auto-column logic w/ fixed table X-Git-Tag: rel_2_0_0b1~458 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3fd1a52794c5463854fe36cbe97595d8489bbf62;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git prevent Mapped[] auto-column logic w/ fixed table When a class has __table__, people will still want to annotate the attributes on classes, so make sure a Mapped annotation without a right side is only interpreted as a column if there is no __table__ Change-Id: I7da4b4c43c4d2c8b6834b781569cb551e75b57b1 --- diff --git a/lib/sqlalchemy/orm/decl_base.py b/lib/sqlalchemy/orm/decl_base.py index 5c72371b29..3fb8af80ca 100644 --- a/lib/sqlalchemy/orm/decl_base.py +++ b/lib/sqlalchemy/orm/decl_base.py @@ -498,6 +498,7 @@ class _ClassScanMapperConfig(_MapperConfig): mapper_args_fn = None table_args = inherited_table_args = None tablename = None + fixed_table = "__table__" in clsdict_view attribute_is_overridden = self._cls_attr_override_checker(self.cls) @@ -666,7 +667,8 @@ class _ClassScanMapperConfig(_MapperConfig): is_dataclass, ) if obj is None: - collected_attributes[name] = MappedColumn() + if not fixed_table: + collected_attributes[name] = MappedColumn() else: collected_attributes[name] = obj else: @@ -701,7 +703,11 @@ class _ClassScanMapperConfig(_MapperConfig): annotation, False, ) - if obj is None and _is_mapped_annotation(annotation, cls): + if ( + obj is None + and not fixed_table + and _is_mapped_annotation(annotation, cls) + ): collected_attributes[name] = MappedColumn() elif name in clsdict_view: collected_attributes[name] = obj diff --git a/test/orm/declarative/test_typed_mapping.py b/test/orm/declarative/test_typed_mapping.py index 71eb7ce42b..57ce969ac8 100644 --- a/test/orm/declarative/test_typed_mapping.py +++ b/test/orm/declarative/test_typed_mapping.py @@ -160,6 +160,39 @@ class MappedColumnTest(fixtures.TestBase, testing.AssertsCompiledSQL): is_true(User.__table__.c.data.nullable) assert isinstance(User.__table__.c.created_at.type, DateTime) + def test_anno_w_fixed_table(self, decl_base): + users = Table( + "users", + decl_base.metadata, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("data", String(50)), + Column("x", Integer), + Column("y", Integer), + Column("created_at", DateTime), + ) + + class User(decl_base): + __table__ = users + + id: Mapped[int] + name: Mapped[str] + data: Mapped[Optional[str]] + x: Mapped[int] + y: Mapped[int] + created_at: Mapped[datetime.datetime] + + self.assert_compile( + select(User), + "SELECT users.id, users.name, users.data, users.x, " + "users.y, users.created_at FROM users", + ) + eq_(User.__mapper__.primary_key, (User.__table__.c.id,)) + is_false(User.__table__.c.id.nullable) + is_false(User.__table__.c.name.nullable) + is_true(User.__table__.c.data.nullable) + assert isinstance(User.__table__.c.created_at.type, DateTime) + def test_construct_lhs_type_missing(self, decl_base): class MyClass: pass @@ -421,6 +454,32 @@ class MixinTest(fixtures.TestBase, testing.AssertsCompiledSQL): # ordering of cols is TODO eq_(A.__table__.c.keys(), ["id", "y", "name", "x"]) + self.assert_compile(select(A), "SELECT a.id, a.y, a.name, a.x FROM a") + + def test_mapped_column_omit_fn_fixed_table(self, decl_base): + class MixinOne: + name: Mapped[str] + x: Mapped[int] + y: Mapped[int] + + a = Table( + "a", + decl_base.metadata, + Column("id", Integer, primary_key=True), + Column("name", String(50), nullable=False), + Column("data", String(50)), + Column("x", Integer), + Column("y", Integer), + ) + + class A(MixinOne, decl_base): + __table__ = a + id: Mapped[int] + + self.assert_compile( + select(A), "SELECT a.id, a.name, a.data, a.x, a.y FROM a" + ) + def test_mc_duplication_plain(self, decl_base): class MixinOne: name: Mapped[str] = mapped_column()