diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-10-26 13:27:21 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-10-26 14:31:28 -0400 |
| commit | 73be84ae46473703dcf7b8d39e9666496fb07c8f (patch) | |
| tree | 28740ff53f1efc30c5273d867f5e377f40a69390 /lib/sqlalchemy/orm | |
| parent | 811979150cd9f1aed3d6de6938b84179b2092b89 (diff) | |
| download | sqlalchemy-73be84ae46473703dcf7b8d39e9666496fb07c8f.tar.gz | |
ensure inherited mapper attrs not interpreted as plain dataclass fields
Fixed issue in new dataclass mapping feature where a column declared on the
decalrative base / abstract base / mixin would leak into the constructor
for an inheriting subclass under some circumstances.
Fixes: #8718
Change-Id: Ic519acf239e2f80541516f10995991cbbbed00bd
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/decl_base.py | 23 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 20 |
2 files changed, 33 insertions, 10 deletions
diff --git a/lib/sqlalchemy/orm/decl_base.py b/lib/sqlalchemy/orm/decl_base.py index 4e79ecc6f..4e02e589b 100644 --- a/lib/sqlalchemy/orm/decl_base.py +++ b/lib/sqlalchemy/orm/decl_base.py @@ -772,7 +772,6 @@ class _ClassScanMapperConfig(_MapperConfig): annotation, is_dataclass_field, ) in local_attributes_for_class(): - if re.match(r"^__.+__$", name): if name == "__mapper_args__": check_decl = _check_declared_props_nocascade( @@ -825,6 +824,7 @@ class _ClassScanMapperConfig(_MapperConfig): "not applying to subclass %s." % (base.__name__, name, base, cls) ) + continue elif base is not cls: # we're a mixin, abstract base, or something that is @@ -990,10 +990,15 @@ class _ClassScanMapperConfig(_MapperConfig): _AttributeOptions._get_arguments_for_make_dataclass( key, anno, + mapped_container, self.collected_attributes.get(key, _NoArg.NO_ARG), ) - for key, anno in ( - (key, mapped_anno if mapped_anno else raw_anno) + for key, anno, mapped_container in ( + ( + key, + mapped_anno if mapped_anno else raw_anno, + mapped_container, + ) for key, ( raw_anno, mapped_container, @@ -1003,7 +1008,6 @@ class _ClassScanMapperConfig(_MapperConfig): ) in self.collected_annotations.items() ) ] - annotations = {} defaults = {} for item in field_list: @@ -1139,7 +1143,6 @@ class _ClassScanMapperConfig(_MapperConfig): # copy mixin columns to the mapped class for name, obj, annotation, is_dataclass in attributes_for_class(): - if ( not fixed_table and obj is None @@ -1154,14 +1157,16 @@ class _ClassScanMapperConfig(_MapperConfig): elif isinstance(obj, (Column, MappedColumn)): - obj = self._collect_annotation(name, annotation, True, obj) - if attribute_is_overridden(name, obj): # if column has been overridden # (like by the InstrumentedAttribute of the - # superclass), skip + # superclass), skip. don't collect the annotation + # either (issue #8718) continue - elif name not in dict_ and not ( + + obj = self._collect_annotation(name, annotation, True, obj) + + if name not in dict_ and not ( "__table__" in dict_ and (getattr(obj, "name", None) or name) in dict_["__table__"].c diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 9903c5f4a..1747bfd9b 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -213,7 +213,11 @@ class _AttributeOptions(NamedTuple): @classmethod def _get_arguments_for_make_dataclass( - cls, key: str, annotation: Type[Any], elem: _T + cls, + key: str, + annotation: Type[Any], + mapped_container: Optional[Any], + elem: _T, ) -> Union[ Tuple[str, Type[Any]], Tuple[str, Type[Any], dataclasses.Field[Any]] ]: @@ -229,7 +233,21 @@ class _AttributeOptions(NamedTuple): elif elem is not _NoArg.NO_ARG: # why is typing not erroring on this? return (key, annotation, elem) + elif mapped_container is not None: + # it's Mapped[], but there's no "element", which means declarative + # did not actually do anything for this field. this shouldn't + # happen. + # previously, this would occur because _scan_attributes would + # skip a field that's on an already mapped superclass, but it + # would still include it in the annotations, leading + # to issue #8718 + + assert False, "Mapped[] received without a mapping declaration" + else: + # plain dataclass field, not mapped. Is only possible + # if __allow_unmapped__ is set up. I can see this mode causing + # problems... return (key, annotation) |
