summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/decl_base.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-06-28 18:55:19 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2022-06-29 11:22:59 -0400
commit9d12d493eb38f958c2d50da28f83ccc6de01f0dc (patch)
tree0063d1e4d2be517d3509d50c1b3f64201fc0ccce /lib/sqlalchemy/orm/decl_base.py
parentf19e50ab75cfc904acef31434c92542f3ab50d61 (diff)
downloadsqlalchemy-9d12d493eb38f958c2d50da28f83ccc6de01f0dc.tar.gz
produce column copies up the whole hierarchy first
Fixed issue where a hierarchy of classes set up as an abstract or mixin declarative classes could not declare standalone columns on a superclass that would then be copied correctly to a :class:`_orm.declared_attr` callable that wanted to make use of them on a descendant class. Originally it looked like this would produce an ordering change, however an adjustment to the flow for produce_column_copies has avoided that for now. Fixes: #8190 Change-Id: I4e2ee74edb110793eb42691c3e4a0e0535fba7e9
Diffstat (limited to 'lib/sqlalchemy/orm/decl_base.py')
-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