summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2018-08-17 11:37:30 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2018-08-17 13:42:02 -0400
commitdaab9ad4cc8861a466a0856d3aec85cb08ab6a17 (patch)
treee99cc9c806d4c5a3df2c7178723f62c34d3fe77f /lib/sqlalchemy/ext
parent7b53d9962f438cebaf82e95b3c8304578545c132 (diff)
downloadsqlalchemy-daab9ad4cc8861a466a0856d3aec85cb08ab6a17.tar.gz
Accommodate for classically mapped base classes in declarative
Fixed issue in previously untested use case, allowing a declarative mapped class to inherit from a classically-mapped class outside of the declarative base, including that it accommodates for unmapped intermediate classes. An unmapped intermediate class may specify ``__abstract__``, which is now interpreted correctly, or the intermediate class can remain unmarked, and the classically mapped base class will be detected within the hierarchy regardless. In order to anticipate existing scenarios which may be mixing in classical mappings into existing declarative hierarchies, an error is now raised if multiple mapped bases are detected for a given class. Fixes: #4321 Change-Id: I8604ecfd170d2589d9d1b1c87ba303762071fc30 (cherry picked from commit 4c931b2ec7e0f09ac8c3ebe28c794f5858d54efb)
Diffstat (limited to 'lib/sqlalchemy/ext')
-rw-r--r--lib/sqlalchemy/ext/declarative/base.py53
1 files changed, 45 insertions, 8 deletions
diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py
index 5d0eab34e..b702fb214 100644
--- a/lib/sqlalchemy/ext/declarative/base.py
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -35,26 +35,50 @@ def _declared_mapping_info(cls):
return None
-def _resolve_for_abstract(cls):
+def _resolve_for_abstract_or_classical(cls):
if cls is object:
return None
if _get_immediate_cls_attr(cls, '__abstract__', strict=True):
for sup in cls.__bases__:
- sup = _resolve_for_abstract(sup)
+ sup = _resolve_for_abstract_or_classical(sup)
if sup is not None:
return sup
else:
return None
else:
+ classical = _dive_for_classically_mapped_class(cls)
+ if classical is not None:
+ return classical
+ else:
+ return cls
+
+
+def _dive_for_classically_mapped_class(cls):
+ # support issue #4321
+
+ # if we are within a base hierarchy, don't
+ # search at all for classical mappings
+ if hasattr(cls, '_decl_class_registry'):
+ return None
+
+ manager = instrumentation.manager_of_class(cls)
+ if manager is not None:
return cls
+ else:
+ for sup in cls.__bases__:
+ mapper = _dive_for_classically_mapped_class(sup)
+ if mapper is not None:
+ return sup
+ else:
+ return None
def _get_immediate_cls_attr(cls, attrname, strict=False):
"""return an attribute of the class that is either present directly
on the class, e.g. not on a superclass, or is from a superclass but
- this superclass is a mixin, that is, not a descendant of
- the declarative base.
+ this superclass is a non-mapped mixin, that is, not a descendant of
+ the declarative base and is also not classically mapped.
This is used to detect attributes that indicate something about
a mapped class independently from any mapped classes that it may
@@ -66,10 +90,14 @@ def _get_immediate_cls_attr(cls, attrname, strict=False):
for base in cls.__mro__:
_is_declarative_inherits = hasattr(base, '_decl_class_registry')
+ _is_classicial_inherits = not _is_declarative_inherits and \
+ _dive_for_classically_mapped_class(base) is not None
+
if attrname in base.__dict__ and (
base is cls or
((base in cls.__bases__ if strict else True)
- and not _is_declarative_inherits)
+ and not _is_declarative_inherits
+ and not _is_classicial_inherits)
):
return getattr(base, attrname)
else:
@@ -451,15 +479,24 @@ class _MapperConfig(object):
cls = self.cls
table_args = self.table_args
declared_columns = self.declared_columns
+
+ # since we search for classical mappings now, search for
+ # multiple mapped bases as well and raise an error.
+ inherits = []
for c in cls.__bases__:
- c = _resolve_for_abstract(c)
+ c = _resolve_for_abstract_or_classical(c)
if c is None:
continue
if _declared_mapping_info(c) is not None and \
not _get_immediate_cls_attr(
c, '_sa_decl_prepare_nocascade', strict=True):
- self.inherits = c
- break
+ inherits.append(c)
+
+ if inherits:
+ if len(inherits) > 1:
+ raise exc.InvalidRequestError(
+ "Class %s has multiple mapped bases: %r" % (cls, inherits))
+ self.inherits = inherits[0]
else:
self.inherits = None