How can one create mixin dataclasses with native dataclass mapping? #9211
Replies: 5 comments 11 replies
-
good news, this is fixed and was released less than five minutes ago in 2.0.1. See https://docs.sqlalchemy.org/en/20/changelog/changelog_20.html#change-c35566767e596bb56954f885049a247d / #9179 |
Beta Was this translation helpful? Give feedback.
-
There is something strange coing on when a base class is not used. I think it's a bug, but I'm not certain: from __future__ import annotations
import dataclasses as dc
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import Mapped
models = orm.registry()
@orm.declarative_mixin
class Model(orm.MappedAsDataclass):
id: Mapped[int] = orm.mapped_column(
sa.Integer, sa.Identity(), init=False, primary_key=True
)
@orm.declared_attr.directive
@classmethod
def __tablename__(cls) -> str:
return cls.__name__.lower()
@models.mapped_as_dataclass
class Book(Model):
publisher: Mapped[str | None] = orm.mapped_column(
sa.String(255),
nullable=True,
default=None,
)
class Base(orm.DeclarativeBase):
pass
class Book2(Model, Base):
publisher: Mapped[str | None] = orm.mapped_column(
sa.String(255),
nullable=True,
default=None,
)
print("with decorator")
print(dc.is_dataclass(Book))
print(Book())
print("with base decorator")
print(dc.is_dataclass(Book2))
print(Book2()) this prints
when not using a base class something is wrong |
Beta Was this translation helpful? Give feedback.
-
@zzzeek @CaselIT thank you for the very quick reply. I've tried the approach using |
Beta Was this translation helpful? Give feedback.
-
@zzzeek I am getting a similar error today where a mixin dataclass field declared with import dataclasses
from typing import Any, Dict, Optional
from sqlalchemy import ForeignKey, Identity, Integer, String, orm
from sqlalchemy.orm import Mapped, mapped_column
class Base(orm.DeclarativeBase):
pass
class Model(orm.MappedAsDataclass):
@orm.declared_attr.directive
@classmethod
def __tablename__(cls) -> str:
return cls.__name__.lower()
@orm.declared_attr.directive
@classmethod
def __mapper_args__(cls) -> Dict[str, Any]:
return {
"polymorphic_identity": cls.__name__,
"polymorphic_on": "polymorphic_type",
}
@orm.declared_attr
@classmethod
def polymorphic_type(cls) -> Mapped[str]:
return mapped_column(
String,
insert_default=cls.__name__,
init=False,
)
class Book(Model, Base):
id: Mapped[int] = mapped_column(
Integer,
Identity(),
primary_key=True,
init=False,
)
fields = dataclasses.fields(Book)
print(Book, {field.name: f"init={field.init}" for field in fields})
class Novel(Book):
id: Mapped[int] = mapped_column(
ForeignKey("book.id"),
primary_key=True,
init=False,
)
description: Mapped[Optional[str]] If you use a debugger you can see that |
Beta Was this translation helpful? Give feedback.
-
chiming in here as it seems we have some more recent issues that touch upon the patterns here, so also see #12733, #12854 in 2.1 with the patch at https://gerrit.sqlalchemy.org/c/sqlalchemy/sqlalchemy/+/6013 we can do mixins with the decorators, but you have to stick to all decorators or all superclasses. so @CaselIT 's example becomes: from __future__ import annotations
import dataclasses as dc
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.orm import Mapped
models = orm.registry()
@orm.unmapped_dataclass
class DecoratorModel:
id: Mapped[int] = orm.mapped_column(
sa.Integer, sa.Identity(), init=False, primary_key=True
)
@orm.declared_attr.directive
@classmethod
def __tablename__(cls) -> str:
return cls.__name__.lower()
@orm.mapped_as_dataclass(models)
class Book(DecoratorModel):
publisher: Mapped[str | None] = orm.mapped_column(
sa.String(255),
nullable=True,
default=None,
)
class MixinModel(orm.MappedAsDataclass):
id: Mapped[int] = orm.mapped_column(
sa.Integer, sa.Identity(), init=False, primary_key=True
)
@orm.declared_attr.directive
@classmethod
def __tablename__(cls) -> str:
return cls.__name__.lower()
class Base(orm.DeclarativeBase):
pass
class Book2(MixinModel, Base):
publisher: Mapped[str | None] = orm.mapped_column(
sa.String(255),
nullable=True,
default=None,
)
print("with decorator")
print(dc.is_dataclass(Book))
print(Book())
print("with base classes")
print(dc.is_dataclass(Book2))
print(Book2()) as we learned in #12733 we can't really mix the decorator and mixin class styles right now due to the mechanics of how class initialization works. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Previously with the declarative dataclass mapping it was quite simple to create mixins to inherit certain fields and support type-hinting. However, when I attempt to do the same with native dataclass mapping, I run into the issue that inherited fields are in reverse order at runtime. For example, the code below raises a
TypeError: non-default argument 'name' follows default argument
.The desired declarative mapping approach is as follows:
How could the mixin approach given above with the declarative dataclass mapping be achieved with the new native mapping approach? Thanks a bunch in advance π
Beta Was this translation helpful? Give feedback.
All reactions