summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorChris Wilson <chris+github@qwirx.com>2018-12-10 16:37:51 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2018-12-10 22:31:21 -0500
commit65ea042302693492cbaee96dfed5e9337e0d96a4 (patch)
tree5faf860067f6360e8d049b3aa09817aa1e8ed0df /lib/sqlalchemy
parent0d4c0c1a279525e09659e21488348718743c33f9 (diff)
downloadsqlalchemy-65ea042302693492cbaee96dfed5e9337e0d96a4.tar.gz
Add before_mapper_configured event
This event is intended to allow a specific mapper to be skipped during the configure step, by returning a value of `.orm.interfaces.EXT_SKIP` which means the mapper will be skipped within this configure run. The "new mappers" flag will remain set in this case and the configure operation will occur again. This event, and its return value, make it possible to query one base while a different one still needs configuration, which cannot be completed at this time. Fixes: #4397 Change-Id: I122e556f6a4ff842ad15315dcf39e19bb7f9a744 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/4403
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/event/registry.py2
-rw-r--r--lib/sqlalchemy/orm/__init__.py1
-rw-r--r--lib/sqlalchemy/orm/base.py1
-rw-r--r--lib/sqlalchemy/orm/events.py71
-rw-r--r--lib/sqlalchemy/orm/interfaces.py3
-rw-r--r--lib/sqlalchemy/orm/mapper.py19
6 files changed, 86 insertions, 11 deletions
diff --git a/lib/sqlalchemy/event/registry.py b/lib/sqlalchemy/event/registry.py
index 758230487..8d4bada0b 100644
--- a/lib/sqlalchemy/event/registry.py
+++ b/lib/sqlalchemy/event/registry.py
@@ -225,7 +225,7 @@ class _EventKey(object):
return self._key in _key_to_collection
def base_listen(self, propagate=False, insert=False,
- named=False):
+ named=False, retval=None):
target, identifier, fn = \
self.dispatch_target, self.identifier, self._listen_fn
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index f279c9b40..1784ea21f 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -25,6 +25,7 @@ from .mapper import (
from .interfaces import (
EXT_CONTINUE,
EXT_STOP,
+ EXT_SKIP,
PropComparator,
)
from .deprecated_interfaces import (
diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py
index 62b4f59a9..deddaa5a4 100644
--- a/lib/sqlalchemy/orm/base.py
+++ b/lib/sqlalchemy/orm/base.py
@@ -149,6 +149,7 @@ _INSTRUMENTOR = ('mapper', 'instrumentor')
EXT_CONTINUE = util.symbol('EXT_CONTINUE')
EXT_STOP = util.symbol('EXT_STOP')
+EXT_SKIP = util.symbol('EXT_SKIP')
ONETOMANY = util.symbol(
'ONETOMANY',
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index f27849e15..c414f548e 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -437,7 +437,9 @@ class _EventsHold(event.RefCollection):
_dispatch_target = None
@classmethod
- def _listen(cls, event_key, raw=False, propagate=False, **kw):
+ def _listen(
+ cls, event_key, raw=False, propagate=False,
+ retval=False, **kw):
target, identifier, fn = \
event_key.dispatch_target, event_key.identifier, event_key.fn
@@ -447,7 +449,7 @@ class _EventsHold(event.RefCollection):
collection = target.all_holds[target.class_] = {}
event.registry._stored_in_collection(event_key, target)
- collection[event_key._key] = (event_key, raw, propagate)
+ collection[event_key._key] = (event_key, raw, propagate, retval)
if propagate:
stack = list(target.class_.__subclasses__())
@@ -459,7 +461,7 @@ class _EventsHold(event.RefCollection):
# we are already going through __subclasses__()
# so leave generic propagate flag False
event_key.with_dispatch_target(subject).\
- listen(raw=raw, propagate=False, **kw)
+ listen(raw=raw, propagate=False, retval=retval, **kw)
def remove(self, event_key):
target, identifier, fn = \
@@ -474,7 +476,7 @@ class _EventsHold(event.RefCollection):
for subclass in class_.__mro__:
if subclass in cls.all_holds:
collection = cls.all_holds[subclass]
- for event_key, raw, propagate in collection.values():
+ for event_key, raw, propagate, retval in collection.values():
if propagate or subclass is class_:
# since we can't be sure in what order different
# classes in a hierarchy are triggered with
@@ -482,7 +484,7 @@ class _EventsHold(event.RefCollection):
# assignment, instead of using the generic propagate
# flag.
event_key.with_dispatch_target(subject).\
- listen(raw=raw, propagate=False)
+ listen(raw=raw, propagate=False, retval=retval)
class _InstanceEventsHold(_EventsHold):
@@ -659,6 +661,54 @@ class MapperEvents(event.Events):
"""
+ def before_mapper_configured(self, mapper, class_):
+ """Called right before a specific mapper is to be configured.
+
+ This event is intended to allow a specific mapper to be skipped during
+ the configure step, by returning the :attr:`.orm.interfaces.EXT_SKIP`
+ symbol which indicates to the :func:`.configure_mappers` call that this
+ particular mapper (or hierarchy of mappers, if ``propagate=True`` is
+ used) should be skipped in the current configuration run. When one or
+ more mappers are skipped, the he "new mappers" flag will remain set,
+ meaning the :func:`.configure_mappers` function will continue to be
+ called when mappers are used, to continue to try to configure all
+ available mappers.
+
+ In comparison to the other configure-level events,
+ :meth:`.MapperEvents.before_configured`,
+ :meth:`.MapperEvents.after_configured`, and
+ :meth:`.MapperEvents.mapper_configured`, the
+ :meth;`.MapperEvents.before_mapper_configured` event provides for a
+ meaningful return value when it is registered with the ``retval=True``
+ parameter.
+
+ .. versionadded:: 1.3
+
+ e.g.::
+
+ from sqlalchemy.orm import EXT_SKIP
+
+ Base = declarative_base()
+
+ DontConfigureBase = declarative_base()
+
+ @event.listens_for(
+ DontConfigureBase,
+ "before_mapper_configured", retval=True, propagate=True)
+ def dont_configure(mapper, cls):
+ return EXT_SKIP
+
+
+ .. seealso::
+
+ :meth:`.MapperEvents.before_configured`
+
+ :meth:`.MapperEvents.after_configured`
+
+ :meth:`.MapperEvents.mapper_configured`
+
+ """
+
def mapper_configured(self, mapper, class_):
r"""Called when a specific mapper has completed its own configuration
within the scope of the :func:`.configure_mappers` call.
@@ -708,6 +758,8 @@ class MapperEvents(event.Events):
:meth:`.MapperEvents.after_configured`
+ :meth:`.MapperEvents.before_mapper_configured`
+
"""
# TODO: need coverage for this event
@@ -734,8 +786,9 @@ class MapperEvents(event.Events):
Contrast this event to :meth:`.MapperEvents.after_configured`,
which is invoked after the series of mappers has been configured,
- as well as :meth:`.MapperEvents.mapper_configured`, which is invoked
- on a per-mapper basis as each one is configured to the extent possible.
+ as well as :meth:`.MapperEvents.before_mapper_configured`
+ and :meth:`.MapperEvents.mapper_configured`, which are both invoked
+ on a per-mapper basis.
Theoretically this event is called once per
application, but is actually called any time new mappers
@@ -757,6 +810,8 @@ class MapperEvents(event.Events):
.. seealso::
+ :meth:`.MapperEvents.before_mapper_configured`
+
:meth:`.MapperEvents.mapper_configured`
:meth:`.MapperEvents.after_configured`
@@ -808,6 +863,8 @@ class MapperEvents(event.Events):
.. seealso::
+ :meth:`.MapperEvents.before_mapper_configured`
+
:meth:`.MapperEvents.mapper_configured`
:meth:`.MapperEvents.before_configured`
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index 95291377b..80d0a6303 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -23,7 +23,7 @@ from __future__ import absolute_import
from .. import util
from ..sql import operators
from .base import (ONETOMANY, MANYTOONE, MANYTOMANY,
- EXT_CONTINUE, EXT_STOP, NOT_EXTENSION)
+ EXT_CONTINUE, EXT_STOP, EXT_SKIP, NOT_EXTENSION)
from .base import InspectionAttr, InspectionAttrInfo, _MappedAttribute
import collections
from .. import inspect
@@ -36,6 +36,7 @@ __all__ = (
'AttributeExtension',
'EXT_CONTINUE',
'EXT_STOP',
+ 'EXT_SKIP',
'ONETOMANY',
'MANYTOMANY',
'MANYTOONE',
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index f2b326359..fa731f729 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -26,7 +26,9 @@ from ..sql import expression, visitors, operators, util as sql_util
from . import instrumentation, attributes, exc as orm_exc, loading
from . import properties
from . import util as orm_util
-from .interfaces import MapperProperty, InspectionAttr, _MappedAttribute
+from .interfaces import MapperProperty, InspectionAttr, _MappedAttribute, \
+ EXT_SKIP
+
from .base import _class_to_mapper, _state_mapper, class_mapper, \
state_str, _INSTRUMENTOR
@@ -3014,6 +3016,8 @@ def configure_mappers():
if not Mapper._new_mappers:
return
+ has_skip = False
+
Mapper.dispatch._for_class(Mapper).before_configured()
# initialize properties on all mappers
# note that _mapper_registry is unordered, which
@@ -3021,6 +3025,15 @@ def configure_mappers():
# the order of mapper compilation
for mapper in list(_mapper_registry):
+ run_configure = None
+ for fn in mapper.dispatch.before_mapper_configured:
+ run_configure = fn(mapper, mapper.class_)
+ if run_configure is EXT_SKIP:
+ has_skip = True
+ break
+ if run_configure is EXT_SKIP:
+ continue
+
if getattr(mapper, '_configure_failed', False):
e = sa_exc.InvalidRequestError(
"One or more mappers failed to initialize - "
@@ -3030,6 +3043,7 @@ def configure_mappers():
% (mapper, mapper._configure_failed))
e._configure_failed = mapper._configure_failed
raise e
+
if not mapper.configured:
try:
mapper._post_configure_properties()
@@ -3042,7 +3056,8 @@ def configure_mappers():
mapper._configure_failed = exc
raise
- Mapper._new_mappers = False
+ if not has_skip:
+ Mapper._new_mappers = False
finally:
_already_compiling = False
finally: