diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2023-01-30 13:28:42 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2023-01-30 15:40:20 -0500 |
| commit | 4d3a50c2f1af894b081c5f64c529da89f35f1839 (patch) | |
| tree | 19e5e5f294c7dd627806c6aeb50d814402dcd4a0 /lib/sqlalchemy | |
| parent | bcb5c850f2a5279bf7a97af6fbf99cc63dffd62f (diff) | |
| download | sqlalchemy-4d3a50c2f1af894b081c5f64c529da89f35f1839.tar.gz | |
MappedAsDataclass applies @dataclasses.dataclass unconditionally
When using the :class:`.MappedAsDataclass` superclass, all classes within
the hierarchy that are subclasses of this class will now be run through the
``@dataclasses.dataclass`` function whether or not they are actually
mapped, so that non-ORM fields declared on non-mapped classes within the
hierarchy will be used when mapped subclasses are turned into dataclasses.
This behavior applies both to intermediary classes mapped with
``__abstract__ = True`` as well as to the user-defined declarative base
itself, assuming :class:`.MappedAsDataclass` is present as a superclass for
these classes.
This allows non-mapped attributes such as ``InitVar`` declarations on
superclasses to be used, without the need to run the
``@dataclasses.dataclass`` decorator explicitly on each non-mapped class.
The new behavior is considered as correct as this is what the :pep:`681`
implementation expects when using a superclass to indicate dataclass
behavior.
Fixes: #9179
Change-Id: Ia01fa9806a27f7c1121bf7eaddf2847cf6dc5313
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/orm/decl_api.py | 14 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/decl_base.py | 12 |
2 files changed, 22 insertions, 4 deletions
diff --git a/lib/sqlalchemy/orm/decl_api.py b/lib/sqlalchemy/orm/decl_api.py index ecd81c7ed..c0bc37008 100644 --- a/lib/sqlalchemy/orm/decl_api.py +++ b/lib/sqlalchemy/orm/decl_api.py @@ -45,6 +45,7 @@ from ._orm_constructors import relationship from ._orm_constructors import synonym from .attributes import InstrumentedAttribute from .base import _inspect_mapped_class +from .base import _is_mapped_class from .base import Mapped from .decl_base import _add_attribute from .decl_base import _as_declarative @@ -596,20 +597,29 @@ class MappedAsDataclass(metaclass=DCTransformDeclarative): "kw_only": kw_only, } + current_transforms: _DataclassArguments + if hasattr(cls, "_sa_apply_dc_transforms"): current = cls._sa_apply_dc_transforms # type: ignore[attr-defined] _ClassScanMapperConfig._assert_dc_arguments(current) - cls._sa_apply_dc_transforms = { + cls._sa_apply_dc_transforms = current_transforms = { # type: ignore # noqa: E501 k: current.get(k, _NoArg.NO_ARG) if v is _NoArg.NO_ARG else v for k, v in apply_dc_transforms.items() } else: - cls._sa_apply_dc_transforms = apply_dc_transforms + cls._sa_apply_dc_transforms = ( + current_transforms + ) = apply_dc_transforms super().__init_subclass__() + if not _is_mapped_class(cls): + _ClassScanMapperConfig._apply_dataclasses_to_any_class( + current_transforms, cls + ) + class DeclarativeBase( inspection.Inspectable[InstanceState[Any]], diff --git a/lib/sqlalchemy/orm/decl_base.py b/lib/sqlalchemy/orm/decl_base.py index 9e8b02359..0462a8945 100644 --- a/lib/sqlalchemy/orm/decl_base.py +++ b/lib/sqlalchemy/orm/decl_base.py @@ -1078,10 +1078,18 @@ class _ClassScanMapperConfig(_MapperConfig): self.cls.__annotations__ = annotations - self._assert_dc_arguments(dataclass_setup_arguments) + self._apply_dataclasses_to_any_class( + dataclass_setup_arguments, self.cls + ) + + @classmethod + def _apply_dataclasses_to_any_class( + cls, dataclass_setup_arguments: _DataclassArguments, klass: Type[_O] + ) -> None: + cls._assert_dc_arguments(dataclass_setup_arguments) dataclasses.dataclass( - self.cls, + klass, **{ k: v for k, v in dataclass_setup_arguments.items() |
