]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
use getattr for metadata, registry and type_annotation_map
authorFederico Caselli <cfederico87@gmail.com>
Tue, 24 Mar 2026 20:08:03 +0000 (21:08 +0100)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 26 Mar 2026 14:01:05 +0000 (10:01 -0400)
The ``metadata``, ``type_annotation_map``, or ``registry`` can now be
set up in a declarative base also via a mixin class, not only by
directly setting them on the subclass like before.
The declarative class setup now uses ``getattr()`` to look for these
attributes, instead of relying only on the class ``__dict__``.

Fixes: #13198
Change-Id: I032f0064aefb36e5c5a2f1186d7edd30d2863047

doc/build/changelog/unreleased_21/13198.rst [new file with mode: 0644]
lib/sqlalchemy/orm/decl_api.py
test/orm/declarative/test_basic.py

diff --git a/doc/build/changelog/unreleased_21/13198.rst b/doc/build/changelog/unreleased_21/13198.rst
new file mode 100644 (file)
index 0000000..a69703a
--- /dev/null
@@ -0,0 +1,9 @@
+.. change::
+    :tags: usecase, orm
+    :tickets: 13198
+
+    The ``metadata``, ``type_annotation_map``, or ``registry`` can now be
+    set up in a declarative base also via a mixin class, not only by
+    directly setting them on the subclass like before.
+    The declarative class setup now uses ``getattr()`` to look for these
+    attributes, instead of relying only on the class ``__dict__``.
index e42cbf3394404710882482ce1e4e4bad04cfdf97..e9423c087d689c88d53d9e57d016bc149207e103 100644 (file)
@@ -529,17 +529,10 @@ def declarative_mixin(cls: Type[_T]) -> Type[_T]:
 
 
 def _setup_declarative_base(cls: Type[Any]) -> None:
-    if "metadata" in cls.__dict__:
-        metadata = cls.__dict__["metadata"]
-    else:
-        metadata = None
-
-    if "type_annotation_map" in cls.__dict__:
-        type_annotation_map = cls.__dict__["type_annotation_map"]
-    else:
-        type_annotation_map = None
+    metadata = getattr(cls, "metadata", None)
+    type_annotation_map = getattr(cls, "type_annotation_map", None)
+    reg = getattr(cls, "registry", None)
 
-    reg = cls.__dict__.get("registry", None)
     if reg is not None:
         if not isinstance(reg, registry):
             raise exc.InvalidRequestError(
index d79417a5c6adee629e4ae2a7f28fdb1c28d7ca01..c56d2cafc27aeb6e52f153730a19692975dd7d19 100644 (file)
@@ -12,6 +12,7 @@ from sqlalchemy import inspect
 from sqlalchemy import Integer
 from sqlalchemy import join
 from sqlalchemy import literal
+from sqlalchemy import MetaData
 from sqlalchemy import select
 from sqlalchemy import String
 from sqlalchemy import testing
@@ -971,6 +972,69 @@ class DeclarativeBaseSetupsTest(fixtures.TestBase):
 
         eq_(User._set_random_keyword_used_here, True)
 
+    @testing.variation(
+        "basetype", ["DeclarativeBase", "DeclarativeBaseNoMeta"]
+    )
+    @testing.variation("arg", ["registry", "meta", "type_map"])
+    @testing.variation("mixin", ["none", "first", "last"])
+    def test_declarative_base_provide_args(self, basetype, arg, mixin):
+        r = registry()
+        m = MetaData()
+        tm = {int: String}
+
+        def class_with_args(name, bases):
+            kw = {}
+            if arg.registry:
+                kw["registry"] = r
+            elif arg.meta:
+                kw["metadata"] = m
+            elif arg.type_map:
+                kw["type_annotation_map"] = tm
+            else:
+                arg.fail()
+            return type(name, bases, kw)
+
+        def make(base):
+            if mixin.first:
+                mixin_cls = class_with_args("Mixin", ())
+
+                class Base(mixin_cls, base):
+                    pass
+
+            elif mixin.last:
+                mixin_cls = class_with_args("Mixin", ())
+
+                class Base(
+                    base,
+                    mixin_cls,
+                ):
+                    pass
+
+            else:
+                Base = class_with_args("Base", (base,))
+            return Base
+
+        if basetype.DeclarativeBase:
+            Base = make(DeclarativeBase)
+        elif basetype.DeclarativeBaseNoMeta:
+            Base = make(DeclarativeBaseNoMeta)
+        else:
+            basetype.fail()
+
+        class MyClass(Base):
+            __tablename__ = "a"
+            id = Column(Integer, primary_key=True)
+            zeta: Mapped[int]
+
+        if arg.registry:
+            is_(Base.registry, r)
+            assertions.in_("a", r.metadata.tables)
+        elif arg.meta:
+            is_(Base.metadata, m)
+            assertions.in_("a", m.tables)
+        elif arg.type_map:
+            eq_(isinstance(MyClass.__table__.c.zeta.type, String), True)
+
     def test_declarative_base_bad_registry(self):
         with assertions.expect_raises_message(
             exc.InvalidRequestError,