summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2022-06-29 16:52:49 +0000
committerGerrit Code Review <gerrit@ci3.zzzcomputing.com>2022-06-29 16:52:49 +0000
commit4270b26afc7d26ec5324b455e02282bea3b96c03 (patch)
treed9de5922cee552b3cd7643655621b74c2091e750 /lib/sqlalchemy
parent67ba6357e4e6dc8619ac63ce1e8f65310b6595bc (diff)
parent9d12d493eb38f958c2d50da28f83ccc6de01f0dc (diff)
downloadsqlalchemy-4270b26afc7d26ec5324b455e02282bea3b96c03.tar.gz
Merge "produce column copies up the whole hierarchy first" into main
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/decl_base.py41
1 files changed, 37 insertions, 4 deletions
diff --git a/lib/sqlalchemy/orm/decl_base.py b/lib/sqlalchemy/orm/decl_base.py
index 1366bedf2..62251fa2b 100644
--- a/lib/sqlalchemy/orm/decl_base.py
+++ b/lib/sqlalchemy/orm/decl_base.py
@@ -714,7 +714,14 @@ class _ClassScanMapperConfig(_MapperConfig):
attribute_is_overridden = self._cls_attr_override_checker(self.cls)
+ bases = []
+
for base in cls.__mro__:
+ # collect bases and make sure standalone columns are copied
+ # to be the column they will ultimately be on the class,
+ # so that declared_attr functions use the right columns.
+ # need to do this all the way up the hierarchy first
+ # (see #8190)
class_mapped = (
base is not cls
@@ -727,10 +734,34 @@ class _ClassScanMapperConfig(_MapperConfig):
local_attributes_for_class = self._cls_attr_resolver(base)
if not class_mapped and base is not cls:
- self._produce_column_copies(
+ locally_collected_columns = self._produce_column_copies(
local_attributes_for_class,
attribute_is_overridden,
)
+ else:
+ locally_collected_columns = {}
+
+ bases.append(
+ (
+ base,
+ class_mapped,
+ local_attributes_for_class,
+ locally_collected_columns,
+ )
+ )
+
+ for (
+ base,
+ class_mapped,
+ local_attributes_for_class,
+ locally_collected_columns,
+ ) in bases:
+
+ # this transfer can also take place as we scan each name
+ # for finer-grained control of how collected_attributes is
+ # populated, as this is what impacts column ordering.
+ # however it's simpler to get it out of the way here.
+ collected_attributes.update(locally_collected_columns)
for (
name,
@@ -738,6 +769,7 @@ 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(
@@ -1096,10 +1128,10 @@ class _ClassScanMapperConfig(_MapperConfig):
[], Iterable[Tuple[str, Any, Any, bool]]
],
attribute_is_overridden: Callable[[str, Any], bool],
- ) -> None:
+ ) -> Dict[str, Union[Column[Any], MappedColumn[Any]]]:
cls = self.cls
dict_ = self.clsdict_view
- collected_attributes = self.collected_attributes
+ locally_collected_attributes = {}
column_copies = self.column_copies
# copy mixin columns to the mapped class
@@ -1132,9 +1164,10 @@ class _ClassScanMapperConfig(_MapperConfig):
)
column_copies[obj] = copy_ = obj._copy()
- collected_attributes[name] = copy_
+ locally_collected_attributes[name] = copy_
setattr(cls, name, copy_)
+ return locally_collected_attributes
def _extract_mappable_attributes(self) -> None:
cls = self.cls