Skip to content

how to put mixins on the declarative base that have their own __init__ ?  #9249

Closed
@zzzeek

Description

@zzzeek
Member

due to #9171

I would expect this to work

from __future__ import annotations

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column


class Mixin:
    def __init__(self, **kw):
        print("mixin!")

class Base(Mixin, DeclarativeBase):
    pass


class A(Base):
    __tablename__ = "a"

    id: Mapped[int] = mapped_column(primary_key=True)
    data: Mapped[str]


A()

Activity

added
bugSomething isn't working
regressionsomething worked and was broken by a change
near-term releaseaddition to the milestone which indicates this should be in a near-term release
on Feb 6, 2023
added this to the 2.0.x milestone on Feb 6, 2023
zzzeek

zzzeek commented on Feb 6, 2023

@zzzeek
MemberAuthor

Not sure on this one. I wanted to use it in the recipe I just posted at #9246. However it doesn't "work" anyway because I can't call super() on the mixin and get the DeclarativeBase initializer anyway (which seems weird? )

class InitializeRelationships:
    """initialize all relationships"""

    def __init__(self, **kw):
        mapper = inspect(self.__class__)
        for arg in mapper.relationships:
            if arg.key not in kw:
                kw.setdefault(
                    arg.key,
                    None if not arg.uselist else arg.collection_class(),
                )
        super().__init__(**kw)

class Model(InitializeRelationships, DeclarativeBase):
    pass

above, super().__init__() is going back down to object.__init__ again. I thought DeclarativeBase has __init__() on it also? but it really can't, because __init__() is part of the base.

If we want mixins on the base to do init we can of course use the init mapper event.

not sure how I want to do this pattern

removed
regressionsomething worked and was broken by a change
on Feb 6, 2023
changed the title [-]regression: mixin on base no longe has init called [/-] [+]how to put mixins on the declarative base that have their own `__init__` ? [/+] on Feb 6, 2023
zzzeek

zzzeek commented on Feb 6, 2023

@zzzeek
MemberAuthor

we can improve the behavior like this:

diff --git a/lib/sqlalchemy/orm/decl_api.py b/lib/sqlalchemy/orm/decl_api.py
index e3e2611da1..5f2b3c6966 100644
--- a/lib/sqlalchemy/orm/decl_api.py
+++ b/lib/sqlalchemy/orm/decl_api.py
@@ -567,7 +567,7 @@ def _setup_declarative_base(cls: Type[Any]) -> None:
     if "metadata" not in cls.__dict__:
         cls.metadata = cls.registry.metadata  # type: ignore
 
-    if "__init__" not in cls.__dict__:
+    if getattr(cls, "__init__", object.__init__) is object.__init__:
         cls.__init__ = cls.registry.constructor

that way at least the super __init__ is called. The super __init__ itself can't call super() again but at least things are less mysterious

sqla-tester

sqla-tester commented on Feb 6, 2023

@sqla-tester
Collaborator

Mike Bayer has proposed a fix for this issue in the main branch:

check for superclasses of user defined init https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/4429

added a commit that references this issue on Feb 9, 2024
b927d95
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingnear-term releaseaddition to the milestone which indicates this should be in a near-term releaseorm

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @zzzeek@sqla-tester

        Issue actions

          how to put mixins on the declarative base that have their own `__init__` ? · Issue #9249 · sqlalchemy/sqlalchemy