From: Federico Caselli Date: Tue, 24 Mar 2026 20:08:03 +0000 (+0100) Subject: use getattr for metadata, registry and type_annotation_map X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a49a4f021ad9c9ff69f5482881b00f831d523a22;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git use getattr for metadata, registry and type_annotation_map 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 --- diff --git a/doc/build/changelog/unreleased_21/13198.rst b/doc/build/changelog/unreleased_21/13198.rst new file mode 100644 index 0000000000..a69703a29f --- /dev/null +++ b/doc/build/changelog/unreleased_21/13198.rst @@ -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__``. diff --git a/lib/sqlalchemy/orm/decl_api.py b/lib/sqlalchemy/orm/decl_api.py index e42cbf3394..e9423c087d 100644 --- a/lib/sqlalchemy/orm/decl_api.py +++ b/lib/sqlalchemy/orm/decl_api.py @@ -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( diff --git a/test/orm/declarative/test_basic.py b/test/orm/declarative/test_basic.py index d79417a5c6..c56d2cafc2 100644 --- a/test/orm/declarative/test_basic.py +++ b/test/orm/declarative/test_basic.py @@ -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,