summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/ext/declarative/api.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2019-01-29 23:53:10 -0600
committerMike Bayer <mike_mp@zzzcomputing.com>2019-01-30 21:06:27 -0600
commit147f6d382c3b6f06c8efa5a24c07c9849473e7af (patch)
treeb3c1288d8d493bba638a3b07fd2bfdc7defa8fa6 /lib/sqlalchemy/ext/declarative/api.py
parentb8f9517cddf41dbb47ae4ad120141c7ab1a29ac5 (diff)
downloadsqlalchemy-147f6d382c3b6f06c8efa5a24c07c9849473e7af.tar.gz
Add informative failure modes to _DeferredMapperConfig
Added some helper exceptions that invoke when a mapping based on :class:`.AbstractConcreteBase`, :class:`.DeferredReflection`, or :class:`.AutoMap` is used before the mapping is ready to be used, which contain descriptive information on the class, rather than falling through into other failure modes that are less informative. Fixes: #4470 Change-Id: I9bc51697f63cedaa7809a0adb17b2398c209e289
Diffstat (limited to 'lib/sqlalchemy/ext/declarative/api.py')
-rw-r--r--lib/sqlalchemy/ext/declarative/api.py69
1 files changed, 69 insertions, 0 deletions
diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py
index 139481ba5..d6a96b728 100644
--- a/lib/sqlalchemy/ext/declarative/api.py
+++ b/lib/sqlalchemy/ext/declarative/api.py
@@ -17,12 +17,15 @@ from .base import _DeferredMapperConfig
from .base import _del_attribute
from .clsregistry import _class_resolver
from ... import exc
+from ... import inspection
from ... import util
from ...orm import attributes
from ...orm import comparable_property
+from ...orm import exc as orm_exc
from ...orm import interfaces
from ...orm import properties
from ...orm import synonym as _orm_synonym
+from ...orm.base import _inspect_mapped_class
from ...orm.base import _mapper_or_none
from ...orm.util import polymorphic_union
from ...schema import MetaData
@@ -507,6 +510,32 @@ class AbstractConcreteBase(ConcreteBase):
and is only used for selecting. Compare to :class:`.ConcreteBase`,
which does create a persisted table for the base class.
+ .. note::
+
+ The :class:`.AbstractConcreteBase` class does not intend to set up the
+ mapping for the base class until all the subclasses have been defined,
+ as it needs to create a mapping against a selectable that will include
+ all subclass tables. In order to achieve this, it waits for the
+ **mapper configuration event** to occur, at which point it scans
+ through all the configured subclasses and sets up a mapping that will
+ query against all subclasses at once.
+
+ While this event is normally invoked automatically, in the case of
+ :class:`.AbstractConcreteBase`, it may be necessary to invoke it
+ explicitly after **all** subclass mappings are defined, if the first
+ operation is to be a query against this base class. To do so, invoke
+ :func:`.configure_mappers` once all the desired classes have been
+ configured::
+
+ from sqlalchemy.orm import configure_mappers
+
+ configure_mappers()
+
+ .. seealso::
+
+ :func:`.orm.configure_mappers`
+
+
Example::
from sqlalchemy.ext.declarative import AbstractConcreteBase
@@ -524,6 +553,8 @@ class AbstractConcreteBase(ConcreteBase):
'polymorphic_identity':'manager',
'concrete':True}
+ configure_mappers()
+
The abstract base class is handled by declarative in a special way;
at class configuration time, it behaves like a declarative mixin
or an ``__abstract__`` base class. Once classes are configured
@@ -560,6 +591,8 @@ class AbstractConcreteBase(ConcreteBase):
'polymorphic_identity':'manager',
'concrete':True}
+ configure_mappers()
+
When we make use of our mappings however, both ``Manager`` and
``Employee`` will have an independently usable ``.company`` attribute::
@@ -635,6 +668,18 @@ class AbstractConcreteBase(ConcreteBase):
if sm and sm.concrete and cls in scls.__bases__:
sm._set_concrete_base(m)
+ @classmethod
+ def _sa_raise_deferred_config(cls):
+ raise orm_exc.UnmappedClassError(
+ cls,
+ msg="Class %s is a subclass of AbstractConcreteBase and "
+ "has a mapping pending until all subclasses are defined. "
+ "Call the sqlalchemy.orm.configure_mappers() function after "
+ "all subclasses have been defined to "
+ "complete the mapping of this class."
+ % orm_exc._safe_cls_name(cls),
+ )
+
class DeferredReflection(object):
"""A helper class for construction of mappings based on
@@ -745,6 +790,16 @@ class DeferredReflection(object):
cls._reflect_table(local_table, engine)
@classmethod
+ def _sa_raise_deferred_config(cls):
+ raise orm_exc.UnmappedClassError(
+ cls,
+ msg="Class %s is a subclass of DeferredReflection. "
+ "Mappings are not produced until the .prepare() "
+ "method is called on the class hierarchy."
+ % orm_exc._safe_cls_name(cls),
+ )
+
+ @classmethod
def _reflect_table(cls, table, engine):
Table(
table.name,
@@ -755,3 +810,17 @@ class DeferredReflection(object):
autoload_with=engine,
schema=table.schema,
)
+
+
+@inspection._inspects(DeclarativeMeta)
+def _inspect_decl_meta(cls):
+ mp = _inspect_mapped_class(cls)
+ if mp is None:
+ if _DeferredMapperConfig.has_cls(cls):
+ _DeferredMapperConfig.raise_unmapped_for_cls(cls)
+ raise orm_exc.UnmappedClassError(
+ cls,
+ msg="Class %s has a deferred mapping on it. It is not yet "
+ "usable as a mapped class." % orm_exc._safe_cls_name(cls),
+ )
+ return mp