diff options
| author | Chris Wilson <chris+github@qwirx.com> | 2018-12-10 16:37:51 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2018-12-10 22:31:21 -0500 |
| commit | 65ea042302693492cbaee96dfed5e9337e0d96a4 (patch) | |
| tree | 5faf860067f6360e8d049b3aa09817aa1e8ed0df /lib/sqlalchemy | |
| parent | 0d4c0c1a279525e09659e21488348718743c33f9 (diff) | |
| download | sqlalchemy-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.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/base.py | 1 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/events.py | 71 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 3 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 19 |
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: |
