summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2023-01-30 13:28:42 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2023-01-30 15:40:20 -0500
commit4d3a50c2f1af894b081c5f64c529da89f35f1839 (patch)
tree19e5e5f294c7dd627806c6aeb50d814402dcd4a0 /lib/sqlalchemy
parentbcb5c850f2a5279bf7a97af6fbf99cc63dffd62f (diff)
downloadsqlalchemy-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.py14
-rw-r--r--lib/sqlalchemy/orm/decl_base.py12
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()