diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-08-17 11:37:30 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-08-17 13:42:02 -0400 |
| commit | daab9ad4cc8861a466a0856d3aec85cb08ab6a17 (patch) | |
| tree | e99cc9c806d4c5a3df2c7178723f62c34d3fe77f /lib/sqlalchemy/ext | |
| parent | 7b53d9962f438cebaf82e95b3c8304578545c132 (diff) | |
| download | sqlalchemy-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.py | 53 |
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 |
