mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-18 22:52:01 -04:00
Merge "reorganize mapper compile/teardown under registry"
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
||||
.. change::
|
||||
:tags: changed, orm
|
||||
:tickets: 5897
|
||||
|
||||
Mapper "configuration", which occurs within the
|
||||
:func:`_orm.configure_mappers` function, is now organized to be on a
|
||||
per-registry basis. This allows for example the mappers within a certain
|
||||
declarative base to be configured, but not those of another base that is
|
||||
also present in memory. The goal is to provide a means of reducing
|
||||
application startup time by only running the "configure" process for sets
|
||||
of mappers that are needed. This also adds the
|
||||
:meth:`_orm.registry.configure` method that will run configure for the
|
||||
mappers local in a particular registry only.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -221,6 +221,7 @@ class MapperProperty(
|
||||
relationships between mappers and perform other post-mapper-creation
|
||||
initialization steps.
|
||||
|
||||
|
||||
"""
|
||||
self._configure_started = True
|
||||
self.do_init()
|
||||
|
||||
+191
-125
@@ -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.
|
||||
have been constructed thus far across all :class:`_orm.registry`
|
||||
collections.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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`.
|
||||
|
||||
The :func:`.configure_mappers` function provides several event hooks
|
||||
that can be used to augment its functionality. These methods include:
|
||||
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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,7 +27,6 @@ from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.orm import subqueryload
|
||||
from sqlalchemy.orm.mapper import _mapper_registry
|
||||
from sqlalchemy.orm.session import _sessions
|
||||
from sqlalchemy.processors import to_decimal_processor_factory
|
||||
from sqlalchemy.processors import to_unicode_processor_factory
|
||||
@@ -237,13 +236,12 @@ def profile_memory(
|
||||
def assert_no_mappers():
|
||||
clear_mappers()
|
||||
gc_collect()
|
||||
assert len(_mapper_registry) == 0
|
||||
|
||||
|
||||
class EnsureZeroed(fixtures.ORMTest):
|
||||
def setup_test(self):
|
||||
_sessions.clear()
|
||||
_mapper_registry.clear()
|
||||
clear_mappers()
|
||||
|
||||
# enable query caching, however make the cache small so that
|
||||
# the tests don't take too long. issues w/ caching include making
|
||||
|
||||
@@ -227,7 +227,7 @@ class ConcreteInhTest(
|
||||
Employee,
|
||||
)
|
||||
|
||||
configure_mappers()
|
||||
Base.registry.configure()
|
||||
|
||||
# no subclasses yet.
|
||||
assert_raises_message(
|
||||
@@ -257,7 +257,7 @@ class ConcreteInhTest(
|
||||
Employee,
|
||||
)
|
||||
|
||||
configure_mappers()
|
||||
Base.registry.configure()
|
||||
|
||||
self.assert_compile(
|
||||
Session().query(Employee),
|
||||
|
||||
@@ -547,8 +547,6 @@ class DeclarativeTest(DeclarativeTestBase):
|
||||
def test_recompile_on_othermapper(self):
|
||||
"""declarative version of the same test in mappers.py"""
|
||||
|
||||
from sqlalchemy.orm import mapperlib
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
@@ -565,10 +563,10 @@ class DeclarativeTest(DeclarativeTestBase):
|
||||
"User", primaryjoin=user_id == User.id, backref="addresses"
|
||||
)
|
||||
|
||||
assert mapperlib.Mapper._new_mappers is True
|
||||
assert User.__mapper__.registry._new_mappers is True
|
||||
u = User() # noqa
|
||||
assert User.addresses
|
||||
assert mapperlib.Mapper._new_mappers is False
|
||||
assert User.__mapper__.registry._new_mappers is False
|
||||
|
||||
def test_string_dependency_resolution(self):
|
||||
class User(Base, fixtures.ComparableEntity):
|
||||
|
||||
+27
-10
@@ -19,13 +19,13 @@ from sqlalchemy.orm import EXT_SKIP
|
||||
from sqlalchemy.orm import instrumentation
|
||||
from sqlalchemy.orm import Mapper
|
||||
from sqlalchemy.orm import mapper
|
||||
from sqlalchemy.orm import mapperlib
|
||||
from sqlalchemy.orm import query
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.orm import subqueryload
|
||||
from sqlalchemy.orm.mapper import _mapper_registry
|
||||
from sqlalchemy.testing import assert_raises
|
||||
from sqlalchemy.testing import assert_raises_message
|
||||
from sqlalchemy.testing import AssertsCompiledSQL
|
||||
@@ -1073,7 +1073,11 @@ class MapperEventsTest(_RemoveListeners, _fixtures.FixtureTest):
|
||||
canary.mock_calls,
|
||||
)
|
||||
|
||||
def test_before_mapper_configured_event(self):
|
||||
@testing.combinations((True,), (False,), argnames="create_dependency")
|
||||
@testing.combinations((True,), (False,), argnames="configure_at_once")
|
||||
def test_before_mapper_configured_event(
|
||||
self, create_dependency, configure_at_once
|
||||
):
|
||||
"""Test [ticket:4397].
|
||||
|
||||
This event is intended to allow a specific mapper to be skipped during
|
||||
@@ -1088,7 +1092,7 @@ class MapperEventsTest(_RemoveListeners, _fixtures.FixtureTest):
|
||||
"""
|
||||
|
||||
User, users = self.classes.User, self.tables.users
|
||||
mapper(User, users)
|
||||
ump = mapper(User, users)
|
||||
|
||||
AnotherBase = declarative_base()
|
||||
|
||||
@@ -1098,12 +1102,18 @@ class MapperEventsTest(_RemoveListeners, _fixtures.FixtureTest):
|
||||
__mapper_args__ = dict(
|
||||
polymorphic_on="species", polymorphic_identity="Animal"
|
||||
)
|
||||
if create_dependency:
|
||||
user_id = Column("user_id", ForeignKey(users.c.id))
|
||||
|
||||
# Register the first classes and create their Mappers:
|
||||
configure_mappers()
|
||||
if not configure_at_once:
|
||||
# Register the first classes and create their Mappers:
|
||||
configure_mappers()
|
||||
|
||||
unconfigured = [m for m in _mapper_registry if not m.configured]
|
||||
eq_(0, len(unconfigured))
|
||||
unconfigured = list(mapperlib._unconfigured_mappers())
|
||||
eq_(0, len(unconfigured))
|
||||
|
||||
if create_dependency:
|
||||
ump.add_property("animal", relationship(Animal))
|
||||
|
||||
# Declare a subclass, table and mapper, which refers to one that has
|
||||
# not been loaded yet (Employer), and therefore cannot be configured:
|
||||
@@ -1111,8 +1121,12 @@ class MapperEventsTest(_RemoveListeners, _fixtures.FixtureTest):
|
||||
nonexistent = relationship("Nonexistent")
|
||||
|
||||
# These new classes should not be configured at this point:
|
||||
unconfigured = [m for m in _mapper_registry if not m.configured]
|
||||
eq_(1, len(unconfigured))
|
||||
unconfigured = list(mapperlib._unconfigured_mappers())
|
||||
|
||||
if configure_at_once:
|
||||
eq_(3, len(unconfigured))
|
||||
else:
|
||||
eq_(1, len(unconfigured))
|
||||
|
||||
# Now try to query User, which is internally consistent. This query
|
||||
# fails by default because Mammal needs to be configured, and cannot
|
||||
@@ -1121,7 +1135,10 @@ class MapperEventsTest(_RemoveListeners, _fixtures.FixtureTest):
|
||||
s = fixture_session()
|
||||
s.query(User)
|
||||
|
||||
assert_raises(sa.exc.InvalidRequestError, probe)
|
||||
if create_dependency:
|
||||
assert_raises(sa.exc.InvalidRequestError, probe)
|
||||
else:
|
||||
probe()
|
||||
|
||||
# If we disable configuring mappers while querying, then it succeeds:
|
||||
@event.listens_for(
|
||||
|
||||
+192
-4
@@ -15,6 +15,7 @@ from sqlalchemy.orm import aliased
|
||||
from sqlalchemy.orm import attributes
|
||||
from sqlalchemy.orm import backref
|
||||
from sqlalchemy.orm import class_mapper
|
||||
from sqlalchemy.orm import clear_mappers
|
||||
from sqlalchemy.orm import column_property
|
||||
from sqlalchemy.orm import composite
|
||||
from sqlalchemy.orm import configure_mappers
|
||||
@@ -31,8 +32,11 @@ from sqlalchemy.testing import assert_raises
|
||||
from sqlalchemy.testing import assert_raises_message
|
||||
from sqlalchemy.testing import AssertsCompiledSQL
|
||||
from sqlalchemy.testing import eq_
|
||||
from sqlalchemy.testing import expect_raises_message
|
||||
from sqlalchemy.testing import fixtures
|
||||
from sqlalchemy.testing import is_
|
||||
from sqlalchemy.testing import is_false
|
||||
from sqlalchemy.testing import is_true
|
||||
from sqlalchemy.testing import ne_
|
||||
from sqlalchemy.testing.fixtures import ComparableMixin
|
||||
from sqlalchemy.testing.fixtures import fixture_session
|
||||
@@ -304,9 +308,9 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
self.classes.User,
|
||||
)
|
||||
|
||||
self.mapper(User, users)
|
||||
mp = self.mapper(User, users)
|
||||
sa.orm.configure_mappers()
|
||||
assert sa.orm.mapperlib.Mapper._new_mappers is False
|
||||
assert mp.registry._new_mappers is False
|
||||
|
||||
m = self.mapper(
|
||||
Address,
|
||||
@@ -315,10 +319,10 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
)
|
||||
|
||||
assert m.configured is False
|
||||
assert sa.orm.mapperlib.Mapper._new_mappers is True
|
||||
assert m.registry._new_mappers is True
|
||||
User()
|
||||
assert User.addresses
|
||||
assert sa.orm.mapperlib.Mapper._new_mappers is False
|
||||
assert m.registry._new_mappers is False
|
||||
|
||||
def test_configure_on_session(self):
|
||||
User, users = self.classes.User, self.tables.users
|
||||
@@ -1810,6 +1814,35 @@ class MapperTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
self.mapper(Address, addresses)
|
||||
configure_mappers()
|
||||
|
||||
@testing.combinations((True,), (False,))
|
||||
def test_registry_configure(self, cascade):
|
||||
User, users = self.classes.User, self.tables.users
|
||||
|
||||
reg1 = registry()
|
||||
ump = reg1.map_imperatively(User, users)
|
||||
|
||||
reg2 = registry()
|
||||
AnotherBase = reg2.generate_base()
|
||||
|
||||
class Animal(AnotherBase):
|
||||
__tablename__ = "animal"
|
||||
species = Column(String(30), primary_key=True)
|
||||
__mapper_args__ = dict(
|
||||
polymorphic_on="species", polymorphic_identity="Animal"
|
||||
)
|
||||
user_id = Column("user_id", ForeignKey(users.c.id))
|
||||
|
||||
ump.add_property("animal", relationship(Animal))
|
||||
|
||||
if cascade:
|
||||
reg1.configure(cascade=True)
|
||||
else:
|
||||
with expect_raises_message(
|
||||
sa.exc.InvalidRequestError,
|
||||
"configure was called with cascade=False",
|
||||
):
|
||||
reg1.configure()
|
||||
|
||||
def test_reconstructor(self):
|
||||
users = self.tables.users
|
||||
|
||||
@@ -2810,3 +2843,158 @@ class ComparatorFactoryTest(_fixtures.FixtureTest, AssertsCompiledSQL):
|
||||
"foobar(users_1.id) = foobar(:foobar_1)",
|
||||
dialect=default.DefaultDialect(),
|
||||
)
|
||||
|
||||
|
||||
class RegistryConfigDisposeTest(fixtures.TestBase):
|
||||
"""test the cascading behavior of registry configure / dispose."""
|
||||
|
||||
@testing.fixture
|
||||
def threeway_fixture(self):
|
||||
reg1 = registry()
|
||||
reg2 = registry()
|
||||
reg3 = registry()
|
||||
|
||||
ab = bc = True
|
||||
|
||||
@reg1.mapped
|
||||
class A(object):
|
||||
__tablename__ = "a"
|
||||
id = Column(Integer, primary_key=True)
|
||||
|
||||
@reg2.mapped
|
||||
class B(object):
|
||||
__tablename__ = "b"
|
||||
id = Column(Integer, primary_key=True)
|
||||
a_id = Column(ForeignKey(A.id))
|
||||
|
||||
@reg3.mapped
|
||||
class C(object):
|
||||
__tablename__ = "c"
|
||||
id = Column(Integer, primary_key=True)
|
||||
b_id = Column(ForeignKey(B.id))
|
||||
|
||||
if ab:
|
||||
A.__mapper__.add_property("b", relationship(B))
|
||||
|
||||
if bc:
|
||||
B.__mapper__.add_property("c", relationship(C))
|
||||
|
||||
yield reg1, reg2, reg3
|
||||
|
||||
clear_mappers()
|
||||
|
||||
@testing.fixture
|
||||
def threeway_configured_fixture(self, threeway_fixture):
|
||||
reg1, reg2, reg3 = threeway_fixture
|
||||
configure_mappers()
|
||||
|
||||
return reg1, reg2, reg3
|
||||
|
||||
@testing.combinations((True,), (False,), argnames="cascade")
|
||||
def test_configure_cascade_on_dependencies(
|
||||
self, threeway_fixture, cascade
|
||||
):
|
||||
reg1, reg2, reg3 = threeway_fixture
|
||||
A, B, C = (
|
||||
reg1._class_registry["A"],
|
||||
reg2._class_registry["B"],
|
||||
reg3._class_registry["C"],
|
||||
)
|
||||
|
||||
is_(reg3._new_mappers, True)
|
||||
is_(reg2._new_mappers, True)
|
||||
is_(reg1._new_mappers, True)
|
||||
|
||||
if cascade:
|
||||
reg1.configure(cascade=True)
|
||||
|
||||
is_(reg3._new_mappers, False)
|
||||
is_(reg2._new_mappers, False)
|
||||
is_(reg1._new_mappers, False)
|
||||
|
||||
is_true(C.__mapper__.configured)
|
||||
is_true(B.__mapper__.configured)
|
||||
is_true(A.__mapper__.configured)
|
||||
else:
|
||||
with testing.expect_raises_message(
|
||||
sa.exc.InvalidRequestError,
|
||||
"configure was called with cascade=False but additional ",
|
||||
):
|
||||
reg1.configure()
|
||||
|
||||
@testing.combinations((True,), (False,), argnames="cascade")
|
||||
def test_configure_cascade_not_on_dependents(
|
||||
self, threeway_fixture, cascade
|
||||
):
|
||||
reg1, reg2, reg3 = threeway_fixture
|
||||
A, B, C = (
|
||||
reg1._class_registry["A"],
|
||||
reg2._class_registry["B"],
|
||||
reg3._class_registry["C"],
|
||||
)
|
||||
|
||||
is_(reg3._new_mappers, True)
|
||||
is_(reg2._new_mappers, True)
|
||||
is_(reg1._new_mappers, True)
|
||||
|
||||
reg3.configure(cascade=cascade)
|
||||
|
||||
is_(reg3._new_mappers, False)
|
||||
is_(reg2._new_mappers, True)
|
||||
is_(reg1._new_mappers, True)
|
||||
|
||||
is_true(C.__mapper__.configured)
|
||||
is_false(B.__mapper__.configured)
|
||||
is_false(A.__mapper__.configured)
|
||||
|
||||
@testing.combinations((True,), (False,), argnames="cascade")
|
||||
def test_dispose_cascade_not_on_dependencies(
|
||||
self, threeway_configured_fixture, cascade
|
||||
):
|
||||
reg1, reg2, reg3 = threeway_configured_fixture
|
||||
A, B, C = (
|
||||
reg1._class_registry["A"],
|
||||
reg2._class_registry["B"],
|
||||
reg3._class_registry["C"],
|
||||
)
|
||||
am, bm, cm = A.__mapper__, B.__mapper__, C.__mapper__
|
||||
|
||||
reg1.dispose(cascade=cascade)
|
||||
|
||||
eq_(reg3.mappers, {cm})
|
||||
eq_(reg2.mappers, {bm})
|
||||
eq_(reg1.mappers, set())
|
||||
|
||||
is_false(cm._dispose_called)
|
||||
is_false(bm._dispose_called)
|
||||
is_true(am._dispose_called)
|
||||
|
||||
@testing.combinations((True,), (False,), argnames="cascade")
|
||||
def test_clear_cascade_not_on_dependents(
|
||||
self, threeway_configured_fixture, cascade
|
||||
):
|
||||
reg1, reg2, reg3 = threeway_configured_fixture
|
||||
A, B, C = (
|
||||
reg1._class_registry["A"],
|
||||
reg2._class_registry["B"],
|
||||
reg3._class_registry["C"],
|
||||
)
|
||||
am, bm, cm = A.__mapper__, B.__mapper__, C.__mapper__
|
||||
|
||||
if cascade:
|
||||
reg3.dispose(cascade=True)
|
||||
|
||||
eq_(reg3.mappers, set())
|
||||
eq_(reg2.mappers, set())
|
||||
eq_(reg1.mappers, set())
|
||||
|
||||
is_true(cm._dispose_called)
|
||||
is_true(bm._dispose_called)
|
||||
is_true(am._dispose_called)
|
||||
else:
|
||||
with testing.expect_raises_message(
|
||||
sa.exc.InvalidRequestError,
|
||||
"Registry has dependent registries that are not disposed; "
|
||||
"pass cascade=True to clear these also",
|
||||
):
|
||||
reg3.dispose()
|
||||
|
||||
Reference in New Issue
Block a user