]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Forward `**kw` in `__init_subclass__()` to super
authorMichael Oliver <michael@michaeloliver.dev>
Tue, 5 Dec 2023 22:24:17 +0000 (17:24 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 6 Dec 2023 20:52:29 +0000 (15:52 -0500)
Modified the ``__init_subclass__()`` method used by
:class:`_orm.MappedAsDataclass`, :class:`_orm.DeclarativeBase`` and
:class:`_orm.DeclarativeBaseNoMeta` to accept arbitrary ``**kw`` and to
propagate them to the ``super()`` call, allowing greater flexibility in
arranging custom superclasses and mixins which make use of
``__init_subclass__()`` keyword arguments.  Pull request courtesy Michael
Oliver.

Fixes: #10732
Closes: #10733
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/10733
Pull-request-sha: 7fdeec1f3224f48213c9c9af5f3e7e5d0904dafa

Change-Id: I955a735d4e23502b5a6b22ac093e391b378edc87

doc/build/changelog/unreleased_20/10732.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mysql/base.py
lib/sqlalchemy/orm/decl_api.py
test/orm/declarative/test_basic.py

diff --git a/doc/build/changelog/unreleased_20/10732.rst b/doc/build/changelog/unreleased_20/10732.rst
new file mode 100644 (file)
index 0000000..0961b05
--- /dev/null
@@ -0,0 +1,12 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 10668
+
+    Modified the ``__init_subclass__()`` method used by
+    :class:`_orm.MappedAsDataclass`, :class:`_orm.DeclarativeBase`` and
+    :class:`_orm.DeclarativeBaseNoMeta` to accept arbitrary ``**kw`` and to
+    propagate them to the ``super()`` call, allowing greater flexibility in
+    arranging custom superclasses and mixins which make use of
+    ``__init_subclass__()`` keyword arguments.  Pull request courtesy Michael
+    Oliver.
+
index 58d7235e017449f5f08e68f779b5b5789711ef30..749d42ea1208fc6c4664de7d33c79749b176eb7f 100644 (file)
@@ -1571,7 +1571,7 @@ class MySQLCompiler(compiler.SQLCompiler):
     def get_select_precolumns(self, select, **kw):
         """Add special MySQL keywords in place of DISTINCT.
 
-        .. deprecated 1.4:: this usage is deprecated.
+        .. deprecated:: 1.4 This usage is deprecated.
            :meth:`_expression.Select.prefix_with` should be used for special
            keywords at the start of a SELECT.
 
index f2039afcd5405c60de7a1b574f71c4dde6e4388a..b1fc80e5f939543e4fcf3e00fa846ca87b51eb5d 100644 (file)
@@ -594,6 +594,7 @@ class MappedAsDataclass(metaclass=DCTransformDeclarative):
         dataclass_callable: Union[
             _NoArg, Callable[..., Type[Any]]
         ] = _NoArg.NO_ARG,
+        **kw: Any,
     ) -> None:
         apply_dc_transforms: _DataclassArguments = {
             "init": init,
@@ -622,7 +623,7 @@ class MappedAsDataclass(metaclass=DCTransformDeclarative):
                 current_transforms
             ) = apply_dc_transforms
 
-        super().__init_subclass__()
+        super().__init_subclass__(**kw)
 
         if not _is_mapped_class(cls):
             new_anno = (
@@ -839,13 +840,13 @@ class DeclarativeBase(
         def __init__(self, **kw: Any):
             ...
 
-    def __init_subclass__(cls) -> None:
+    def __init_subclass__(cls, **kw: Any) -> None:
         if DeclarativeBase in cls.__bases__:
             _check_not_declarative(cls, DeclarativeBase)
             _setup_declarative_base(cls)
         else:
             _as_declarative(cls._sa_registry, cls, cls.__dict__)
-        super().__init_subclass__()
+        super().__init_subclass__(**kw)
 
 
 def _check_not_declarative(cls: Type[Any], base: Type[Any]) -> None:
@@ -964,12 +965,13 @@ class DeclarativeBaseNoMeta(
         def __init__(self, **kw: Any):
             ...
 
-    def __init_subclass__(cls) -> None:
+    def __init_subclass__(cls, **kw: Any) -> None:
         if DeclarativeBaseNoMeta in cls.__bases__:
             _check_not_declarative(cls, DeclarativeBaseNoMeta)
             _setup_declarative_base(cls)
         else:
             _as_declarative(cls._sa_registry, cls, cls.__dict__)
+        super().__init_subclass__(**kw)
 
 
 def add_mapped_attribute(
index 7085b2af9f6b5a8fb3393520a89c7b361364047e..37a1b643c1d919722d71e433f2166cb031c87c8e 100644 (file)
@@ -35,6 +35,7 @@ from sqlalchemy.orm import exc as orm_exc
 from sqlalchemy.orm import joinedload
 from sqlalchemy.orm import Mapped
 from sqlalchemy.orm import mapped_column
+from sqlalchemy.orm import MappedAsDataclass
 from sqlalchemy.orm import MappedColumn
 from sqlalchemy.orm import Mapper
 from sqlalchemy.orm import registry
@@ -930,6 +931,42 @@ class DeclarativeBaseSetupsTest(fixtures.TestBase):
         # Check to see if __init_subclass__ works in supported versions
         eq_(UserType._set_random_keyword_used_here, True)
 
+    @testing.variation(
+        "basetype",
+        ["DeclarativeBase", "DeclarativeBaseNoMeta", "MappedAsDataclass"],
+    )
+    def test_kw_support_in_declarative_base(self, basetype):
+        """test #10732"""
+
+        if basetype.DeclarativeBase:
+
+            class Base(DeclarativeBase):
+                pass
+
+        elif basetype.DeclarativeBaseNoMeta:
+
+            class Base(DeclarativeBaseNoMeta):
+                pass
+
+        elif basetype.MappedAsDataclass:
+
+            class Base(MappedAsDataclass):
+                pass
+
+        else:
+            basetype.fail()
+
+        class Mixin:
+            def __init_subclass__(cls, random_keyword: bool, **kw) -> None:
+                super().__init_subclass__(**kw)
+                cls._set_random_keyword_used_here = random_keyword
+
+        class User(Base, Mixin, random_keyword=True):
+            __tablename__ = "user"
+            id_ = Column(Integer, primary_key=True)
+
+        eq_(User._set_random_keyword_used_here, True)
+
     def test_declarative_base_bad_registry(self):
         with assertions.expect_raises_message(
             exc.InvalidRequestError,