From: Michael Oliver Date: Tue, 5 Dec 2023 22:24:17 +0000 (-0500) Subject: Forward `**kw` in `__init_subclass__()` to super X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ceeaaecd2401d2407b60c22708f58a8ae0898d85;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Forward `**kw` in `__init_subclass__()` to super 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 --- diff --git a/doc/build/changelog/unreleased_20/10732.rst b/doc/build/changelog/unreleased_20/10732.rst new file mode 100644 index 0000000000..0961b05d73 --- /dev/null +++ b/doc/build/changelog/unreleased_20/10732.rst @@ -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. + diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index 58d7235e01..749d42ea12 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -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. diff --git a/lib/sqlalchemy/orm/decl_api.py b/lib/sqlalchemy/orm/decl_api.py index f2039afcd5..b1fc80e5f9 100644 --- a/lib/sqlalchemy/orm/decl_api.py +++ b/lib/sqlalchemy/orm/decl_api.py @@ -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( diff --git a/test/orm/declarative/test_basic.py b/test/orm/declarative/test_basic.py index 7085b2af9f..37a1b643c1 100644 --- a/test/orm/declarative/test_basic.py +++ b/test/orm/declarative/test_basic.py @@ -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,