summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2021-02-01 23:13:42 +0000
committerGerrit Code Review <gerrit@bbpush.zzzcomputing.com>2021-02-01 23:13:42 +0000
commit45c8fe6c0a755aa7a777dd0c12f97bb52dcc00a7 (patch)
tree2420ca661194e1d7cbac87854c970b45d1ba490f /lib
parente917041d1e5d6349372a80222e6d9e32cfa07082 (diff)
parent5ec5b0a6c7b618bba7926e21f77be9557973860f (diff)
downloadsqlalchemy-45c8fe6c0a755aa7a777dd0c12f97bb52dcc00a7.tar.gz
Merge "reorganize mapper compile/teardown under registry"
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/orm/__init__.py15
-rw-r--r--lib/sqlalchemy/orm/base.py5
-rw-r--r--lib/sqlalchemy/orm/decl_api.py173
-rw-r--r--lib/sqlalchemy/orm/instrumentation.py15
-rw-r--r--lib/sqlalchemy/orm/interfaces.py1
-rw-r--r--lib/sqlalchemy/orm/mapper.py322
-rw-r--r--lib/sqlalchemy/orm/relationships.py16
7 files changed, 389 insertions, 158 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index f6b1a2e93..ffbe78503 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -43,7 +43,6 @@ from .interfaces import ONETOMANY # noqa
from .interfaces import PropComparator # noqa
from .loading import merge_frozen_result # noqa
from .loading import merge_result # noqa
-from .mapper import _mapper_registry
from .mapper import class_mapper # noqa
from .mapper import configure_mappers # noqa
from .mapper import Mapper # noqa
@@ -247,6 +246,10 @@ synonym = public_factory(SynonymProperty, ".orm.synonym")
def clear_mappers():
"""Remove all mappers from all classes.
+ .. versionchanged:: 1.4 This function now locates all
+ :class:`_orm.registry` objects and calls upon the
+ :meth:`_orm.registry.dispose` method of each.
+
This function removes all instrumentation from classes and disposes
of their associated mappers. Once called, the classes are unmapped
and can be later re-mapped with new mappers.
@@ -266,14 +269,8 @@ def clear_mappers():
"""
with mapperlib._CONFIGURE_MUTEX:
- while _mapper_registry:
- try:
- mapper, b = _mapper_registry.popitem()
- except KeyError:
- # weak registry, item could have been collected
- pass
- else:
- mapper.dispose()
+ all_regs = mapperlib._all_registries()
+ mapperlib._dispose_registries(all_regs, False)
joinedload = strategy_options.joinedload._unbound_fn
diff --git a/lib/sqlalchemy/orm/base.py b/lib/sqlalchemy/orm/base.py
index b805c6f93..2932f1bb9 100644
--- a/lib/sqlalchemy/orm/base.py
+++ b/lib/sqlalchemy/orm/base.py
@@ -158,7 +158,6 @@ PASSIVE_ONLY_PERSISTENT = util.symbol(
DEFAULT_MANAGER_ATTR = "_sa_class_manager"
DEFAULT_STATE_ATTR = "_sa_instance_state"
-_INSTRUMENTOR = ("mapper", "instrumentor")
EXT_CONTINUE = util.symbol("EXT_CONTINUE")
EXT_STOP = util.symbol("EXT_STOP")
@@ -412,8 +411,8 @@ def _inspect_mapped_class(class_, configure=False):
except exc.NO_STATE:
return None
else:
- if configure and mapper._new_mappers:
- mapper._configure_all()
+ if configure:
+ mapper._check_configure()
return mapper
diff --git a/lib/sqlalchemy/orm/decl_api.py b/lib/sqlalchemy/orm/decl_api.py
index e6983675b..e6d41083f 100644
--- a/lib/sqlalchemy/orm/decl_api.py
+++ b/lib/sqlalchemy/orm/decl_api.py
@@ -7,13 +7,16 @@
"""Public API functions and helpers for declarative."""
from __future__ import absolute_import
+import itertools
import re
import weakref
from . import attributes
from . import clsregistry
from . import exc as orm_exc
+from . import instrumentation
from . import interfaces
+from . import mapper as mapperlib
from .base import _inspect_mapped_class
from .decl_base import _add_attribute
from .decl_base import _as_declarative
@@ -456,12 +459,179 @@ class registry(object):
class_registry = weakref.WeakValueDictionary()
self._class_registry = class_registry
+ self._managers = weakref.WeakKeyDictionary()
+ self._non_primary_mappers = weakref.WeakKeyDictionary()
self.metadata = lcl_metadata
self.constructor = constructor
+ self._dependents = set()
+ self._dependencies = set()
+
+ self._new_mappers = False
+
+ mapperlib._mapper_registries[self] = True
+
+ @property
+ def mappers(self):
+ """read only collection of all :class:`_orm.Mapper` objects."""
+
+ return frozenset(manager.mapper for manager in self._managers).union(
+ self._non_primary_mappers
+ )
+
+ def _set_depends_on(self, registry):
+ if registry is self:
+ return
+ registry._dependents.add(self)
+ self._dependencies.add(registry)
+
+ def _flag_new_mapper(self, mapper):
+ mapper._ready_for_configure = True
+ if self._new_mappers:
+ return
+
+ for reg in self._recurse_with_dependents({self}):
+ reg._new_mappers = True
+
+ @classmethod
+ def _recurse_with_dependents(cls, registries):
+ todo = registries
+ done = set()
+ while todo:
+ reg = todo.pop()
+ done.add(reg)
+
+ # if yielding would remove dependents, make sure we have
+ # them before
+ todo.update(reg._dependents.difference(done))
+ yield reg
+
+ # if yielding would add dependents, make sure we have them
+ # after
+ todo.update(reg._dependents.difference(done))
+
+ @classmethod
+ def _recurse_with_dependencies(cls, registries):
+ todo = registries
+ done = set()
+ while todo:
+ reg = todo.pop()
+ done.add(reg)
+
+ # if yielding would remove dependencies, make sure we have
+ # them before
+ todo.update(reg._dependencies.difference(done))
+
+ yield reg
+
+ # if yielding would remove dependencies, make sure we have
+ # them before
+ todo.update(reg._dependencies.difference(done))
+
+ def _mappers_to_configure(self):
+ return itertools.chain(
+ (
+ manager.mapper
+ for manager in self._managers
+ if manager.is_mapped
+ and not manager.mapper.configured
+ and manager.mapper._ready_for_configure
+ ),
+ (
+ npm
+ for npm in self._non_primary_mappers
+ if not npm.configured and npm._ready_for_configure
+ ),
+ )
+
+ def _add_non_primary_mapper(self, np_mapper):
+ self._non_primary_mappers[np_mapper] = True
+
def _dispose_cls(self, cls):
clsregistry.remove_class(cls.__name__, cls, self._class_registry)
+ def _add_manager(self, manager):
+ self._managers[manager] = True
+ assert manager.registry is None
+ manager.registry = self
+
+ def configure(self, cascade=False):
+ """Configure all as-yet unconfigured mappers in this
+ :class:`_orm.registry`.
+
+ The configure step is used to reconcile and initialize the
+ :func:`_orm.relationship` linkages between mapped classes, as well as
+ to invoke configuration events such as the
+ :meth:`_orm.MapperEvents.before_configured` and
+ :meth:`_orm.MapperEvents.after_configured`, which may be used by ORM
+ extensions or user-defined extension hooks.
+
+ If one or more mappers in this registry contain
+ :func:`_orm.relationship` constructs that refer to mapped classes in
+ other registries, this registry is said to be *dependent* on those
+ registries. In order to configure those dependent registries
+ automatically, the :paramref:`_orm.registry.configure.cascade` flag
+ should be set to ``True``. Otherwise, if they are not configured, an
+ exception will be raised. The rationale behind this behavior is to
+ allow an application to programmatically invoke configuration of
+ registries while controlling whether or not the process implicitly
+ reaches other registries.
+
+ As an alternative to invoking :meth:`_orm.registry.configure`, the ORM
+ function :func:`_orm.configure_mappers` function may be used to ensure
+ configuration is complete for all :class:`_orm.registry` objects in
+ memory. This is generally simpler to use and also predates the usage of
+ :class:`_orm.registry` objects overall. However, this function will
+ impact all mappings throughout the running Python process and may be
+ more memory/time consuming for an application that has many registries
+ in use for different purposes that may not be needed immediately.
+
+ .. seealso::
+
+ :func:`_orm.configure_mappers`
+
+
+ .. versionadded:: 1.4.0b2
+
+ """
+ mapperlib._configure_registries({self}, cascade=cascade)
+
+ def dispose(self, cascade=False):
+ """Dispose of all mappers in this :class:`_orm.registry`.
+
+ After invocation, all the classes that were mapped within this registry
+ will no longer have class instrumentation associated with them. This
+ method is the per-:class:`_orm.registry` analogue to the
+ application-wide :func:`_orm.clear_mappers` function.
+
+ If this registry contains mappers that are dependencies of other
+ registries, typically via :func:`_orm.relationship` links, then those
+ registries must be disposed as well. When such registries exist in
+ relation to this one, their :meth:`_orm.registry.dispose` method will
+ also be called, if the :paramref:`_orm.registry.dispose.cascade` flag
+ is set to ``True``; otherwise, an error is raised if those registries
+ were not already disposed.
+
+ .. versionadded:: 1.4.0b2
+
+ .. seealso::
+
+ :func:`_orm.clear_mappers`
+
+ """
+
+ mapperlib._dispose_registries({self}, cascade=cascade)
+
+ def _dispose_manager_and_mapper(self, manager):
+ if "mapper" in manager.__dict__:
+ mapper = manager.mapper
+
+ mapper._set_dispose_flags()
+
+ class_ = manager.class_
+ self._dispose_cls(class_)
+ instrumentation._instrumentation_factory.unregister(class_)
+
def generate_base(
self,
mapper=None,
@@ -707,6 +877,9 @@ class registry(object):
return _mapper(self, class_, local_table, kw)
+mapperlib._legacy_registry = registry()
+
+
@util.deprecated_params(
bind=(
"2.0",
diff --git a/lib/sqlalchemy/orm/instrumentation.py b/lib/sqlalchemy/orm/instrumentation.py
index d2ff72180..c970bee22 100644
--- a/lib/sqlalchemy/orm/instrumentation.py
+++ b/lib/sqlalchemy/orm/instrumentation.py
@@ -129,7 +129,7 @@ class ClassManager(HasMemoized, dict):
if mapper:
self.mapper = mapper
if registry:
- self.registry = registry
+ registry._add_manager(self)
if declarative_scan:
self.declarative_scan = declarative_scan
if expired_attribute_loader:
@@ -278,11 +278,6 @@ class ClassManager(HasMemoized, dict):
setattr(self.class_, self.MANAGER_ATTR, self)
- def dispose(self):
- """Disassociate this manager from its class."""
-
- delattr(self.class_, self.MANAGER_ATTR)
-
@util.hybridmethod
def manager_getter(self):
return _default_manager_getter
@@ -359,6 +354,9 @@ class ClassManager(HasMemoized, dict):
if key in self.local_attrs:
self.uninstrument_attribute(key)
+ if self.MANAGER_ATTR in self.class_.__dict__:
+ delattr(self.class_, self.MANAGER_ATTR)
+
def install_descriptor(self, key, inst):
if key in (self.STATE_ATTR, self.MANAGER_ATTR):
raise KeyError(
@@ -496,7 +494,7 @@ class _SerializeManager(object):
"Python process!" % self.class_,
)
elif manager.is_mapped and not manager.mapper.configured:
- manager.mapper._configure_all()
+ manager.mapper._check_configure()
# setup _sa_instance_state ahead of time so that
# unpickle events can access the object normally.
@@ -538,10 +536,7 @@ class InstrumentationFactory(object):
def unregister(self, class_):
manager = manager_of_class(class_)
manager.unregister()
- manager.dispose()
self.dispatch.class_uninstrument(class_)
- if ClassManager.MANAGER_ATTR in class_.__dict__:
- delattr(class_, ClassManager.MANAGER_ATTR)
# this attribute is replaced by sqlalchemy.ext.instrumentation
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index bbe39af60..994a76bdc 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -221,6 +221,7 @@ class MapperProperty(
relationships between mappers and perform other post-mapper-creation
initialization steps.
+
"""
self._configure_started = True
self.do_init()
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 4ad91dd06..66c062724 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -29,7 +29,6 @@ from . import loading
from . import properties
from . import util as orm_util
from .base import _class_to_mapper
-from .base import _INSTRUMENTOR
from .base import _state_mapper
from .base import class_mapper
from .base import state_str
@@ -57,8 +56,21 @@ from ..sql import visitors
from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL
from ..util import HasMemoized
+_mapper_registries = weakref.WeakKeyDictionary()
+
+_legacy_registry = None
+
+
+def _all_registries():
+ return set(_mapper_registries)
+
+
+def _unconfigured_mappers():
+ for reg in _mapper_registries:
+ for mapper in reg._mappers_to_configure():
+ yield mapper
+
-_mapper_registry = weakref.WeakKeyDictionary()
_already_compiling = False
@@ -111,8 +123,8 @@ class Mapper(
"""
- _new_mappers = False
_dispose_called = False
+ _ready_for_configure = False
@util.deprecated_params(
non_primary=(
@@ -567,7 +579,6 @@ class Mapper(
techniques.
"""
-
self.class_ = util.assert_arg_type(class_, type, "class_")
self._sort_key = "%s.%s" % (
self.class_.__module__,
@@ -620,7 +631,7 @@ class Mapper(
else None
)
self._dependency_processors = []
- self.validators = util.immutabledict()
+ self.validators = util.EMPTY_DICT
self.passive_updates = passive_updates
self.passive_deletes = passive_deletes
self.legacy_is_orphan = legacy_is_orphan
@@ -662,8 +673,6 @@ class Mapper(
else:
self.exclude_properties = None
- self.configured = False
-
# prevent this mapper from being constructed
# while a configure_mappers() is occurring (and defer a
# configure_mappers() until construction succeeds)
@@ -674,7 +683,7 @@ class Mapper(
self._configure_properties()
self._configure_polymorphic_setter()
self._configure_pks()
- Mapper._new_mappers = True
+ self.registry._flag_new_mapper(self)
self._log("constructed")
self._expire_memoizations()
@@ -768,7 +777,7 @@ class Mapper(
"""
- configured = None
+ configured = False
"""Represent ``True`` if this :class:`_orm.Mapper` has been configured.
This is a *read only* attribute determined during mapper construction.
@@ -1191,8 +1200,9 @@ class Mapper(
"Mapper." % self.class_
)
self.class_manager = manager
+ self.registry = manager.registry
self._identity_class = manager.mapper._identity_class
- _mapper_registry[self] = True
+ manager.registry._add_non_primary_mapper(self)
return
if manager is not None:
@@ -1207,8 +1217,6 @@ class Mapper(
# ClassManager.instrument_attribute() creates
# new managers for each subclass if they don't yet exist.
- _mapper_registry[self] = True
-
self.dispatch.instrument_class(self, self.class_)
# this invokes the class_instrument event and sets up
@@ -1227,6 +1235,7 @@ class Mapper(
# and call the class_instrument event
finalize=True,
)
+
if not manager.registry:
util.warn_deprecated_20(
"Calling the mapper() function directly outside of a "
@@ -1234,18 +1243,17 @@ class Mapper(
" Please use the sqlalchemy.orm.registry.map_imperatively() "
"function for a classical mapping."
)
- from . import registry
-
- manager.registry = registry()
+ assert _legacy_registry is not None
+ _legacy_registry._add_manager(manager)
self.class_manager = manager
+ self.registry = manager.registry
# The remaining members can be added by any mapper,
# e_name None or not.
- if manager.info.get(_INSTRUMENTOR, False):
+ if manager.mapper is None:
return
- event.listen(manager, "first_init", _event_on_first_init, raw=True)
event.listen(manager, "init", _event_on_init, raw=True)
for key, method in util.iterate_attributes(self.class_):
@@ -1270,29 +1278,12 @@ class Mapper(
{name: (method, validation_opts)}
)
- manager.info[_INSTRUMENTOR] = self
-
- @classmethod
- def _configure_all(cls):
- """Class-level path to the :func:`.configure_mappers` call."""
- configure_mappers()
-
- def dispose(self):
- # Disable any attribute-based compilation.
+ def _set_dispose_flags(self):
self.configured = True
+ self._ready_for_configure = True
self._dispose_called = True
- if hasattr(self, "_configure_failed"):
- del self._configure_failed
-
- if (
- not self.non_primary
- and self.class_manager is not None
- and self.class_manager.is_mapped
- and self.class_manager.mapper is self
- ):
- self.class_manager.registry._dispose_cls(self.class_)
- instrumentation.unregister_class(self.class_)
+ self.__dict__.pop("_configure_failed", None)
def _configure_pks(self):
self.tables = sql_util.find_tables(self.persist_selectable)
@@ -1877,6 +1868,10 @@ class Mapper(
"columns get mapped." % (key, self, column.key, prop)
)
+ def _check_configure(self):
+ if self.registry._new_mappers:
+ _configure_registries({self.registry}, cascade=True)
+
def _post_configure_properties(self):
"""Call the ``init()`` method on all ``MapperProperties``
attached to this mapper.
@@ -1983,8 +1978,8 @@ class Mapper(
def get_property(self, key, _configure_mappers=True):
"""return a MapperProperty associated with the given key."""
- if _configure_mappers and Mapper._new_mappers:
- configure_mappers()
+ if _configure_mappers:
+ self._check_configure()
try:
return self._props[key]
@@ -2005,8 +2000,8 @@ class Mapper(
@property
def iterate_properties(self):
"""return an iterator of all MapperProperty objects."""
- if Mapper._new_mappers:
- configure_mappers()
+
+ self._check_configure()
return iter(self._props.values())
def _mappers_from_spec(self, spec, selectable):
@@ -2081,8 +2076,8 @@ class Mapper(
@HasMemoized.memoized_attribute
def _with_polymorphic_mappers(self):
- if Mapper._new_mappers:
- configure_mappers()
+ self._check_configure()
+
if not self.with_polymorphic:
return []
return self._mappers_from_spec(*self.with_polymorphic)
@@ -2098,8 +2093,7 @@ class Mapper(
This allows the inspection process run a configure mappers hook.
"""
- if Mapper._new_mappers:
- configure_mappers()
+ self._check_configure()
@HasMemoized.memoized_attribute
def _with_polymorphic_selectable(self):
@@ -2403,8 +2397,8 @@ class Mapper(
:attr:`_orm.Mapper.all_orm_descriptors`
"""
- if Mapper._new_mappers:
- configure_mappers()
+
+ self._check_configure()
return util.ImmutableProperties(self._props)
@HasMemoized.memoized_attribute
@@ -2559,8 +2553,7 @@ class Mapper(
)
def _filter_properties(self, type_):
- if Mapper._new_mappers:
- configure_mappers()
+ self._check_configure()
return util.ImmutableProperties(
util.OrderedDict(
(k, v) for k, v in self._props.items() if isinstance(v, type_)
@@ -3288,24 +3281,54 @@ class _OptGetColumnsNotAvailable(Exception):
def configure_mappers():
"""Initialize the inter-mapper relationships of all mappers that
- have been constructed thus far.
-
- This function can be called any number of times, but in
- most cases is invoked automatically, the first time mappings are used,
- as well as whenever mappings are used and additional not-yet-configured
- mappers have been constructed.
-
- Points at which this occur include when a mapped class is instantiated
- into an instance, as well as when the :meth:`.Session.query` method
- is used.
-
- The :func:`.configure_mappers` function provides several event hooks
- that can be used to augment its functionality. These methods include:
+ have been constructed thus far across all :class:`_orm.registry`
+ collections.
+
+ The configure step is used to reconcile and initialize the
+ :func:`_orm.relationship` linkages between mapped classes, as well as to
+ invoke configuration events such as the
+ :meth:`_orm.MapperEvents.before_configured` and
+ :meth:`_orm.MapperEvents.after_configured`, which may be used by ORM
+ extensions or user-defined extension hooks.
+
+ Mapper configuration is normally invoked automatically, the first time
+ mappings from a particular :class:`_orm.registry` are used, as well as
+ whenever mappings are used and additional not-yet-configured mappers have
+ been constructed. The automatic configuration process however is local only
+ to the :class:`_orm.registry` involving the target mapper and any related
+ :class:`_orm.registry` objects which it may depend on; this is
+ equivalent to invoking the :meth:`_orm.registry.configure` method
+ on a particular :class:`_orm.registry`.
+
+ By contrast, the :func:`_orm.configure_mappers` function will invoke the
+ configuration process on all :class:`_orm.registry` objects that
+ exist in memory, and may be useful for scenarios where many individual
+ :class:`_orm.registry` objects that are nonetheless interrelated are
+ in use.
+
+ .. versionchanged:: 1.4
+
+ As of SQLAlchemy 1.4.0b2, this function works on a
+ per-:class:`_orm.registry` basis, locating all :class:`_orm.registry`
+ objects present and invoking the :meth:`_orm.registry.configure` method
+ on each. The :meth:`_orm.registry.configure` method may be preferred to
+ limit the configuration of mappers to those local to a particular
+ :class:`_orm.registry` and/or declarative base class.
+
+ Points at which automatic configuration is invoked include when a mapped
+ class is instantiated into an instance, as well as when ORM queries
+ are emitted using :meth:`.Session.query` or :meth:`_orm.Session.execute`
+ with an ORM-enabled statement.
+
+ The mapper configure process, whether invoked by
+ :func:`_orm.configure_mappers` or from :meth:`_orm.registry.configure`,
+ provides several event hooks that can be used to augment the mapper
+ configuration step. These hooks include:
* :meth:`.MapperEvents.before_configured` - called once before
- :func:`.configure_mappers` does any work; this can be used to establish
- additional options, properties, or related mappings before the operation
- proceeds.
+ :func:`.configure_mappers` or :meth:`_orm.registry.configure` does any
+ work; this can be used to establish additional options, properties, or
+ related mappings before the operation proceeds.
* :meth:`.MapperEvents.mapper_configured` - called as each individual
:class:`_orm.Mapper` is configured within the process; will include all
@@ -3313,15 +3336,25 @@ def configure_mappers():
to be configured.
* :meth:`.MapperEvents.after_configured` - called once after
- :func:`.configure_mappers` is complete; at this stage, all
- :class:`_orm.Mapper` objects that are known to SQLAlchemy will be fully
- configured. Note that the calling application may still have other
- mappings that haven't been produced yet, such as if they are in modules
- as yet unimported.
+ :func:`.configure_mappers` or :meth:`_orm.registry.configure` is
+ complete; at this stage, all :class:`_orm.Mapper` objects that fall
+ within the scope of the configuration operation will be fully configured.
+ Note that the calling application may still have other mappings that
+ haven't been produced yet, such as if they are in modules as yet
+ unimported, and may also have mappings that are still to be configured,
+ if they are in other :class:`_orm.registry` collections not part of the
+ current scope of configuration.
"""
- if not Mapper._new_mappers:
+ _configure_registries(set(_mapper_registries), cascade=True)
+
+
+def _configure_registries(registries, cascade):
+ for reg in registries:
+ if reg._new_mappers:
+ break
+ else:
return
with _CONFIGURE_MUTEX:
@@ -3332,58 +3365,105 @@ def configure_mappers():
try:
# double-check inside mutex
- if not Mapper._new_mappers:
+ for reg in registries:
+ if reg._new_mappers:
+ break
+ else:
return
- has_skip = False
-
Mapper.dispatch._for_class(Mapper).before_configured()
# initialize properties on all mappers
# note that _mapper_registry is unordered, which
# may randomly conceal/reveal issues related to
# 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 - "
- "can't proceed with initialization of other "
- "mappers. Triggering mapper: '%s'. "
- "Original exception was: %s"
- % (mapper, mapper._configure_failed)
- )
- e._configure_failed = mapper._configure_failed
- raise e
-
- if not mapper.configured:
- try:
- mapper._post_configure_properties()
- mapper._expire_memoizations()
- mapper.dispatch.mapper_configured(
- mapper, mapper.class_
- )
- except Exception:
- exc = sys.exc_info()[1]
- if not hasattr(exc, "_configure_failed"):
- mapper._configure_failed = exc
- raise
-
- if not has_skip:
- Mapper._new_mappers = False
+ _do_configure_registries(registries, cascade)
finally:
_already_compiling = False
Mapper.dispatch._for_class(Mapper).after_configured()
+@util.preload_module("sqlalchemy.orm.decl_api")
+def _do_configure_registries(registries, cascade):
+
+ registry = util.preloaded.orm_decl_api.registry
+
+ orig = set(registries)
+
+ for reg in registry._recurse_with_dependencies(registries):
+ has_skip = False
+
+ for mapper in reg._mappers_to_configure():
+ 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 - "
+ "can't proceed with initialization of other "
+ "mappers. Triggering mapper: '%s'. "
+ "Original exception was: %s"
+ % (mapper, mapper._configure_failed)
+ )
+ e._configure_failed = mapper._configure_failed
+ raise e
+
+ if not mapper.configured:
+ try:
+ mapper._post_configure_properties()
+ mapper._expire_memoizations()
+ mapper.dispatch.mapper_configured(mapper, mapper.class_)
+ except Exception:
+ exc = sys.exc_info()[1]
+ if not hasattr(exc, "_configure_failed"):
+ mapper._configure_failed = exc
+ raise
+ if not has_skip:
+ reg._new_mappers = False
+
+ if not cascade and reg._dependencies.difference(orig):
+ raise sa_exc.InvalidRequestError(
+ "configure was called with cascade=False but "
+ "additional registries remain"
+ )
+
+
+@util.preload_module("sqlalchemy.orm.decl_api")
+def _dispose_registries(registries, cascade):
+
+ registry = util.preloaded.orm_decl_api.registry
+
+ orig = set(registries)
+
+ for reg in registry._recurse_with_dependents(registries):
+ if not cascade and reg._dependents.difference(orig):
+ raise sa_exc.InvalidRequestError(
+ "Registry has dependent registries that are not disposed; "
+ "pass cascade=True to clear these also"
+ )
+
+ while reg._managers:
+ manager, _ = reg._managers.popitem()
+ reg._dispose_manager_and_mapper(manager)
+
+ reg._non_primary_mappers.clear()
+ reg._dependents.clear()
+ for dep in reg._dependencies:
+ dep._dependents.discard(reg)
+ reg._dependencies.clear()
+ # this wasn't done in the 1.3 clear_mappers() and in fact it
+ # was a bug, as it could cause configure_mappers() to invoke
+ # the "before_configured" event even though mappers had all been
+ # disposed.
+ reg._new_mappers = False
+
+
def reconstructor(fn):
"""Decorate a method as the 'reconstructor' hook.
@@ -3460,25 +3540,12 @@ def validates(*names, **kw):
def _event_on_load(state, ctx):
- instrumenting_mapper = state.manager.info[_INSTRUMENTOR]
+ instrumenting_mapper = state.manager.mapper
+
if instrumenting_mapper._reconstructor:
instrumenting_mapper._reconstructor(state.obj())
-def _event_on_first_init(manager, cls):
- """Initial mapper compilation trigger.
-
- instrumentation calls this one when InstanceState
- is first generated, and is needed for legacy mutable
- attributes to work.
- """
-
- instrumenting_mapper = manager.info.get(_INSTRUMENTOR)
- if instrumenting_mapper:
- if Mapper._new_mappers:
- configure_mappers()
-
-
def _event_on_init(state, args, kwargs):
"""Run init_instance hooks.
@@ -3488,10 +3555,9 @@ def _event_on_init(state, args, kwargs):
"""
- instrumenting_mapper = state.manager.info.get(_INSTRUMENTOR)
+ instrumenting_mapper = state.manager.mapper
if instrumenting_mapper:
- if Mapper._new_mappers:
- configure_mappers()
+ instrumenting_mapper._check_configure()
if instrumenting_mapper._set_polymorphic_identity:
instrumenting_mapper._set_polymorphic_identity(state)
diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
index 41c3a5e53..1a0140914 100644
--- a/lib/sqlalchemy/orm/relationships.py
+++ b/lib/sqlalchemy/orm/relationships.py
@@ -1658,11 +1658,8 @@ class RelationshipProperty(StrategizedProperty):
return _orm_annotate(self.__negated_contains_or_equals(other))
@util.memoized_property
- @util.preload_module("sqlalchemy.orm.mapper")
def property(self):
- mapperlib = util.preloaded.orm_mapper
- if mapperlib.Mapper._new_mappers:
- mapperlib.Mapper._configure_all()
+ self.prop.parent._check_configure()
return self.prop
def _with_parent(self, instance, alias_secondary=True, from_entity=None):
@@ -2130,9 +2127,9 @@ class RelationshipProperty(StrategizedProperty):
return self.entity.mapper
def do_init(self):
-
self._check_conflicts()
self._process_dependent_arguments()
+ self._setup_registry_dependencies()
self._setup_join_conditions()
self._check_cascade_settings(self._cascade)
self._post_init()
@@ -2141,6 +2138,11 @@ class RelationshipProperty(StrategizedProperty):
super(RelationshipProperty, self).do_init()
self._lazy_strategy = self._get_strategy((("lazy", "select"),))
+ def _setup_registry_dependencies(self):
+ self.parent.mapper.registry._set_depends_on(
+ self.entity.mapper.registry
+ )
+
def _process_dependent_arguments(self):
"""Convert incoming configuration arguments to their
proper form.
@@ -3391,9 +3393,7 @@ class JoinCondition(object):
_track_overlapping_sync_targets = weakref.WeakKeyDictionary()
- @util.preload_module("sqlalchemy.orm.mapper")
def _warn_for_conflicting_sync_targets(self):
- mapperlib = util.preloaded.orm_mapper
if not self.support_sync:
return
@@ -3424,7 +3424,7 @@ class JoinCondition(object):
for pr, fr_ in prop_to_from.items():
if (
- pr.mapper in mapperlib._mapper_registry
+ not pr.mapper._dispose_called
and pr not in self.prop._reverse_property
and pr.key not in self.prop._overlaps
and self.prop.key not in pr._overlaps