summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2023-03-30 14:48:39 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2023-03-30 17:12:48 -0400
commit8eaf6f30cb8b316a6a6b4bb8743322f03b4903c4 (patch)
tree06214da20a99ce89b0b2a0f31464f06ba336a8fa /lib/sqlalchemy/orm
parent72d2ec57928bdf5649c551bdaa87b7fb0943c2fe (diff)
downloadsqlalchemy-8eaf6f30cb8b316a6a6b4bb8743322f03b4903c4.tar.gz
warn for DC mixin / abstract fields that are not on a dataclass
Fields that are declared on Declarative Mixins and then combined with classes that make use of :class:`_orm.MappedAsDataclass`, where those mixin fields are not themselves part of a dataclass, now emit a deprecation warning as these fields will be ignored in a future release, as Python dataclasses behavior is to ignore these fields. Type checkers will not see these fields under pep-681. Fixes: #9350 Change-Id: Ie0a0ac0f0bb58d1c2aae13b4a8dcd0439a4f5477
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/decl_base.py63
1 files changed, 52 insertions, 11 deletions
diff --git a/lib/sqlalchemy/orm/decl_base.py b/lib/sqlalchemy/orm/decl_base.py
index 6be514276..f59b1c90e 100644
--- a/lib/sqlalchemy/orm/decl_base.py
+++ b/lib/sqlalchemy/orm/decl_base.py
@@ -440,6 +440,7 @@ class _CollectedAnnotation(NamedTuple):
is_dataclass: bool
attr_value: Any
originating_module: str
+ originating_class: Type[Any]
class _ClassScanMapperConfig(_MapperConfig):
@@ -1051,6 +1052,19 @@ class _ClassScanMapperConfig(_MapperConfig):
"'@registry.mapped_as_dataclass'"
)
+ warn_for_non_dc_attrs = collections.defaultdict(list)
+
+ def _allow_dataclass_field(
+ key: str, originating_class: Type[Any]
+ ) -> bool:
+ if (
+ originating_class is not self.cls
+ and "__dataclass_fields__" not in originating_class.__dict__
+ ):
+ warn_for_non_dc_attrs[originating_class].append(key)
+
+ return True
+
manager = instrumentation.manager_of_class(self.cls)
assert manager is not None
@@ -1074,20 +1088,45 @@ class _ClassScanMapperConfig(_MapperConfig):
is_dc,
attr_value,
originating_module,
+ originating_class,
) in self.collected_annotations.items()
- if key not in self.collected_attributes
- # issue #9226; check for attributes that we've collected
- # which are already instrumented, which we would assume
- # mean we are in an ORM inheritance mapping and this attribute
- # is already mapped on the superclass. Under no circumstance
- # should any QueryableAttribute be sent to the dataclass()
- # function; anything that's mapped should be Field and
- # that's it
- or not isinstance(
- self.collected_attributes[key], QueryableAttribute
+ if _allow_dataclass_field(key, originating_class)
+ and (
+ key not in self.collected_attributes
+ # issue #9226; check for attributes that we've collected
+ # which are already instrumented, which we would assume
+ # mean we are in an ORM inheritance mapping and this
+ # attribute is already mapped on the superclass. Under
+ # no circumstance should any QueryableAttribute be sent to
+ # the dataclass() function; anything that's mapped should
+ # be Field and that's it
+ or not isinstance(
+ self.collected_attributes[key], QueryableAttribute
+ )
)
)
]
+
+ if warn_for_non_dc_attrs:
+ for (
+ originating_class,
+ non_dc_attrs,
+ ) in warn_for_non_dc_attrs.items():
+ util.warn_deprecated(
+ f"When transforming {self.cls} to a dataclass, "
+ f"attribute(s) "
+ f"{', '.join(repr(key) for key in non_dc_attrs)} "
+ f"originates from superclass "
+ f"{originating_class}, which is not a dataclass. This "
+ f"usage is deprecated and will raise an error in "
+ f"SQLAlchemy 2.1. When declaring SQLAlchemy Declarative "
+ f"Dataclasses, ensure that all mixin classes and other "
+ f"superclasses which include attributes are also a "
+ f"subclass of MappedAsDataclass.",
+ "2.0",
+ code="dcmx",
+ )
+
annotations = {}
defaults = {}
for item in field_list:
@@ -1271,6 +1310,7 @@ class _ClassScanMapperConfig(_MapperConfig):
is_dataclass,
attr_value,
originating_class.__module__,
+ originating_class,
)
return ca
@@ -1468,8 +1508,9 @@ class _ClassScanMapperConfig(_MapperConfig):
is_dataclass,
attr_value,
originating_module,
+ originating_class,
) = self.collected_annotations.get(
- k, (None, None, None, False, None, None)
+ k, (None, None, None, False, None, None, None)
)
# issue #8692 - don't do any annotation interpretation if