]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Simplified module pre-loading strategy and made it linter friendly
authorFederico Caselli <cfederico87@gmail.com>
Sat, 7 Mar 2020 18:17:07 +0000 (19:17 +0100)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 7 Mar 2020 22:50:45 +0000 (17:50 -0500)
Introduced a modules registry to register modules that should be lazily loaded
in the package init. This ensures that they are in the system module cache,
avoiding potential thread safety issues as when importing them directly
in the function that uses them. The module registry is used to obtain
these modules directly, ensuring that the all the lazily loaded modules
are resolved at the proper time

This replaces dependency_for decorator and the dependencies decorator logic,
removing the need to pass the resolved modules as arguments of the
decodated functions and removes possible errors caused by linters.

Fixes: #4689
Fixes: #4656
Change-Id: I2e291eba4297867fc0ddb5d875b9f7af34751d01

38 files changed:
doc/build/orm/internals.rst
examples/versioned_history/history_meta.py
lib/sqlalchemy/__init__.py
lib/sqlalchemy/ext/__init__.py
lib/sqlalchemy/ext/declarative/api.py
lib/sqlalchemy/ext/declarative/base.py
lib/sqlalchemy/ext/declarative/clsregistry.py
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/descriptor_props.py
lib/sqlalchemy/orm/dynamic.py
lib/sqlalchemy/orm/events.py
lib/sqlalchemy/orm/exc.py
lib/sqlalchemy/orm/loading.py
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/orm/persistence.py
lib/sqlalchemy/orm/properties.py
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/relationships.py
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/orm/state.py
lib/sqlalchemy/orm/strategies.py
lib/sqlalchemy/orm/unitofwork.py
lib/sqlalchemy/sql/__init__.py
lib/sqlalchemy/sql/base.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/elements.py
lib/sqlalchemy/sql/schema.py
lib/sqlalchemy/sql/selectable.py
lib/sqlalchemy/sql/sqltypes.py
lib/sqlalchemy/sql/traversals.py
lib/sqlalchemy/sql/type_api.py
lib/sqlalchemy/util/__init__.py
lib/sqlalchemy/util/langhelpers.py
test/base/test_utils.py
test/ext/declarative/test_basic.py
test/orm/test_mapper.py
test/profiles.txt
test/test_deprecations.py

index 2658e24eee3edd4dc94ec7e9c7ce4f91241cdeaa..fe35178a6959b89e6395a70ceeabab0c30147d6d 100644 (file)
@@ -21,7 +21,7 @@ sections, are listed here.
 .. autoclass:: sqlalchemy.orm.properties.ColumnProperty
     :members:
 
-.. autoclass:: sqlalchemy.orm.properties.ComparableProperty
+.. autoclass:: sqlalchemy.orm.descriptor_props.ComparableProperty
     :members:
 
 .. autoclass:: sqlalchemy.orm.descriptor_props.CompositeProperty
@@ -84,7 +84,7 @@ sections, are listed here.
     :members:
     :inherited-members:
 
-.. autoclass:: sqlalchemy.orm.properties.RelationshipProperty
+.. autoclass:: sqlalchemy.orm.relationships.RelationshipProperty
     :members:
     :inherited-members:
 
index eb824bd7b66e57421e996e879ee25d9f1a641ecd..f2b3f8118d997f99dcec0ea24014858c46422d60 100644 (file)
@@ -14,7 +14,7 @@ from sqlalchemy.orm import attributes
 from sqlalchemy.orm import mapper
 from sqlalchemy.orm import object_mapper
 from sqlalchemy.orm.exc import UnmappedColumnError
-from sqlalchemy.orm.properties import RelationshipProperty
+from sqlalchemy.orm.relationships import RelationshipProperty
 
 
 def col_references_table(col, table):
index 2d67db24cf3ff71107b1a6187bc0b6cbb9455d9e..0f18aba3318d063f7e6e945cb1ac1527bd9eb5e9 100644 (file)
@@ -139,7 +139,7 @@ def __go(lcls):
         if not (name.startswith("_") or _inspect.ismodule(obj))
     )
 
-    _sa_util.dependencies.resolve_all("sqlalchemy")
+    _sa_util.preloaded.import_prefix("sqlalchemy")
 
 
 __go(locals())
index 247301b94f08f84e392d9893f6e9d2f768255bff..1f842fc2a933ae05ed8d3678bda4582300627ef3 100644 (file)
@@ -8,4 +8,4 @@
 from .. import util as _sa_util
 
 
-_sa_util.dependencies.resolve_all("sqlalchemy.ext")
+_sa_util.preloaded.import_prefix("sqlalchemy.ext")
index c8695b7bff31be97bf868a2aa9b82770f56e32be..ca7d3a022b9e4d3c199a7fe1f3f3fe6c9019fd59 100644 (file)
@@ -23,7 +23,7 @@ from ...orm import attributes
 from ...orm import comparable_property
 from ...orm import exc as orm_exc
 from ...orm import interfaces
-from ...orm import properties
+from ...orm import relationships
 from ...orm import synonym as _orm_synonym
 from ...orm.base import _inspect_mapped_class
 from ...orm.base import _mapper_or_none
@@ -761,7 +761,7 @@ class DeferredReflection(object):
             metadata = mapper.class_.metadata
             for rel in mapper._props.values():
                 if (
-                    isinstance(rel, properties.RelationshipProperty)
+                    isinstance(rel, relationships.RelationshipProperty)
                     and rel.secondary is not None
                 ):
                     if isinstance(rel.secondary, Table):
index 51ba35b4b7bc574b0533c5abce08d849cd90cdcb..314e96cf10ffde830f93bc54580b55eb7a2a7b29 100644 (file)
@@ -22,9 +22,9 @@ from ...orm import synonym
 from ...orm.attributes import QueryableAttribute
 from ...orm.base import _is_mapped_class
 from ...orm.base import InspectionAttr
+from ...orm.descriptor_props import CompositeProperty
 from ...orm.interfaces import MapperProperty
 from ...orm.properties import ColumnProperty
-from ...orm.properties import CompositeProperty
 from ...schema import Column
 from ...schema import Table
 from ...sql import expression
index 93e643cf5c3670cf649c61d8fe08794052f894c9..71594aae7c3a3cc00217b58ec38064b2c3b50763 100644 (file)
@@ -16,10 +16,10 @@ from ... import exc
 from ... import inspection
 from ... import util
 from ...orm import class_mapper
+from ...orm import ColumnProperty
 from ...orm import interfaces
-from ...orm.properties import ColumnProperty
-from ...orm.properties import RelationshipProperty
-from ...orm.properties import SynonymProperty
+from ...orm import RelationshipProperty
+from ...orm import SynonymProperty
 from ...schema import _get_table_key
 
 
index 307f55ad0a3f65c392696b7b8e20eec11e1197cb..b5ebb93e0004f46eef390a2a344b81a8888095a1 100644 (file)
@@ -276,8 +276,8 @@ def __go(lcls):
         if not (name.startswith("_") or _inspect.ismodule(obj))
     )
 
-    _sa_util.dependencies.resolve_all("sqlalchemy.orm")
-    _sa_util.dependencies.resolve_all("sqlalchemy.ext")
+    _sa_util.preloaded.import_prefix("sqlalchemy.orm")
+    _sa_util.preloaded.import_prefix("sqlalchemy.ext")
 
 
 __go(locals())
index bebf72a9d0d6a18b85de387982f3476009091113..c2067c228d42cdc29255d7a40482ff900352ba50 100644 (file)
@@ -12,7 +12,6 @@ as actively in the load/persist ORM loop.
 """
 
 from . import attributes
-from . import properties
 from . import query
 from .interfaces import MapperProperty
 from .interfaces import PropComparator
@@ -85,7 +84,6 @@ class DescriptorProperty(MapperProperty):
         mapper.class_manager.instrument_attribute(self.key, proxy_attr)
 
 
-@util.langhelpers.dependency_for("sqlalchemy.orm.properties", add_to_all=True)
 class CompositeProperty(DescriptorProperty):
     """Defines a "composite" mapped attribute, representing a collection
     of columns as one attribute.
@@ -463,7 +461,6 @@ class CompositeProperty(DescriptorProperty):
         return str(self.parent.class_.__name__) + "." + self.key
 
 
-@util.langhelpers.dependency_for("sqlalchemy.orm.properties", add_to_all=True)
 class ConcreteInheritedProperty(DescriptorProperty):
     """A 'do nothing' :class:`.MapperProperty` that disables
     an attribute on a concrete subclass that is only present
@@ -517,7 +514,6 @@ class ConcreteInheritedProperty(DescriptorProperty):
         self.descriptor = NoninheritedConcreteProp()
 
 
-@util.langhelpers.dependency_for("sqlalchemy.orm.properties", add_to_all=True)
 class SynonymProperty(DescriptorProperty):
     def __init__(
         self,
@@ -672,7 +668,10 @@ class SynonymProperty(DescriptorProperty):
         attr = getattr(self.parent.class_, self.name)
         return attr.impl.get_history(*arg, **kw)
 
+    @util.preload_module("sqlalchemy.orm.properties")
     def set_parent(self, parent, init):
+        properties = util.preloaded.orm_properties
+
         if self.map_column:
             # implement the 'map_column' option.
             if self.key not in parent.persist_selectable.c:
@@ -708,7 +707,6 @@ class SynonymProperty(DescriptorProperty):
         self.parent = parent
 
 
-@util.langhelpers.dependency_for("sqlalchemy.orm.properties", add_to_all=True)
 @util.deprecated_cls(
     "0.7",
     ":func:`.comparable_property` is deprecated and will be removed in a "
index a8d2731db00f6b7a86384f537c95530796b63660..2a3ef54dd0008afe829f8c302bb601ebaa35ba0f 100644 (file)
@@ -17,7 +17,7 @@ from . import exc as orm_exc
 from . import interfaces
 from . import object_mapper
 from . import object_session
-from . import properties
+from . import relationships
 from . import strategies
 from . import util as orm_util
 from .query import Query
@@ -27,7 +27,7 @@ from .. import util
 
 
 @log.class_logger
-@properties.RelationshipProperty.strategy_for(lazy="dynamic")
+@relationships.RelationshipProperty.strategy_for(lazy="dynamic")
 class DynaLoader(strategies.AbstractRelationshipLoader):
     def init_class_attribute(self, mapper):
         self.is_class_level = True
index f0db1d86f804239dbfbf2d82100af9ba2c706027..4a50f64644f0ec0987a68e80cf972b16067f4367 100644 (file)
@@ -183,8 +183,10 @@ class InstanceEvents(event.Events):
         _InstanceEventsHold.populate(class_, classmanager)
 
     @classmethod
-    @util.dependencies("sqlalchemy.orm")
-    def _accept_with(cls, orm, target):
+    @util.preload_module("sqlalchemy.orm")
+    def _accept_with(cls, target):
+        orm = util.preloaded.orm
+
         if isinstance(target, instrumentation.ClassManager):
             return target
         elif isinstance(target, mapperlib.Mapper):
@@ -665,8 +667,10 @@ class MapperEvents(event.Events):
         _MapperEventsHold.populate(class_, mapper)
 
     @classmethod
-    @util.dependencies("sqlalchemy.orm")
-    def _accept_with(cls, orm, target):
+    @util.preload_module("sqlalchemy.orm")
+    def _accept_with(cls, target):
+        orm = util.preloaded.orm
+
         if target is orm.mapper:
             return mapperlib.Mapper
         elif isinstance(target, type):
index f009b9c0b10a44a4b88a8de9f6253bde27996772..0c256548764be1efe43ab5269e2c95f82c59bf1a 100644 (file)
@@ -67,8 +67,10 @@ class DetachedInstanceError(sa_exc.SQLAlchemyError):
 class UnmappedInstanceError(UnmappedError):
     """An mapping operation was requested for an unknown instance."""
 
-    @util.dependencies("sqlalchemy.orm.base")
-    def __init__(self, base, obj, msg=None):
+    @util.preload_module("sqlalchemy.orm.base")
+    def __init__(self, obj, msg=None):
+        base = util.preloaded.orm_base
+
         if not msg:
             try:
                 base.class_mapper(type(obj))
@@ -124,8 +126,10 @@ class ObjectDeletedError(sa_exc.InvalidRequestError):
 
     """
 
-    @util.dependencies("sqlalchemy.orm.base")
-    def __init__(self, base, state, msg=None):
+    @util.preload_module("sqlalchemy.orm.base")
+    def __init__(self, state, msg=None):
+        base = util.preloaded.orm_base
+
         if not msg:
             msg = (
                 "Instance '%s' has been deleted, or its "
@@ -192,8 +196,10 @@ def _safe_cls_name(cls):
     return cls_name
 
 
-@util.dependencies("sqlalchemy.orm.base")
-def _default_unmapped(base, cls):
+@util.preload_module("sqlalchemy.orm.base")
+def _default_unmapped(cls):
+    base = util.preloaded.orm_base
+
     try:
         mappers = base.manager_of_class(cls).mappers
     except NO_STATE:
index d943ebb19097c62028ca640e66114858d377aaf9..c3d4773cd3ba3b18a164122fec3c4990c80175bd 100644 (file)
@@ -104,9 +104,10 @@ def instances(query, cursor, context):
             cursor.close()
 
 
-@util.dependencies("sqlalchemy.orm.query")
-def merge_result(querylib, query, iterator, load=True):
+@util.preload_module("sqlalchemy.orm.query")
+def merge_result(query, iterator, load=True):
     """Merge a result into this :class:`.Query` object's Session."""
+    querylib = util.preloaded.orm_query
 
     session = query.session
     if load:
index 91e3251e2c29e8694cf06c0f2908b8877a4d6ad7..5619d2e16eeb94f0542eb0731ee043a6f30d8380 100644 (file)
@@ -1659,7 +1659,10 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
     def _prop_set(self):
         return frozenset(self._props.values())
 
+    @util.preload_module("sqlalchemy.orm.descriptor_props")
     def _adapt_inherited_property(self, key, prop, init):
+        descriptor_props = util.preloaded.orm_descriptor_props
+
         if not self.concrete:
             self._configure_property(key, prop, init=False, setparent=False)
         elif key not in self._props:
@@ -1680,12 +1683,14 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
             ):
                 self._configure_property(
                     key,
-                    properties.ConcreteInheritedProperty(),
+                    descriptor_props.ConcreteInheritedProperty(),
                     init=init,
                     setparent=True,
                 )
 
+    @util.preload_module("sqlalchemy.orm.descriptor_props")
     def _configure_property(self, key, prop, init=True, setparent=True):
+        descriptor_props = util.preloaded.orm_descriptor_props
         self._log("_configure_property(%s, %s)", key, prop.__class__.__name__)
 
         if not isinstance(prop, MapperProperty):
@@ -1769,7 +1774,7 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
                 self._props[key],
                 (
                     properties.ColumnProperty,
-                    properties.ConcreteInheritedProperty,
+                    descriptor_props.ConcreteInheritedProperty,
                 ),
             )
         ):
@@ -1796,10 +1801,11 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
         if self.configured:
             self._expire_memoizations()
 
+    @util.preload_module("sqlalchemy.orm.descriptor_props")
     def _property_from_column(self, key, prop):
-        """generate/update a :class:`.ColumnProprerty` given a
+        """generate/update a :class:`.ColumnProperty` given a
         :class:`.Column` object. """
-
+        descriptor_props = util.preloaded.orm_descriptor_props
         # we were passed a Column or a list of Columns;
         # generate a properties.ColumnProperty
         columns = util.to_list(prop)
@@ -1841,7 +1847,7 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
             )
             return prop
         elif prop is None or isinstance(
-            prop, properties.ConcreteInheritedProperty
+            prop, descriptor_props.ConcreteInheritedProperty
         ):
             mapped_column = []
             for c in columns:
@@ -2374,6 +2380,7 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
         )
 
     @_memoized_configured_property
+    @util.preload_module("sqlalchemy.orm.descriptor_props")
     def synonyms(self):
         """Return a namespace of all :class:`.SynonymProperty`
         properties maintained by this :class:`.Mapper`.
@@ -2384,7 +2391,9 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
             objects.
 
         """
-        return self._filter_properties(properties.SynonymProperty)
+        descriptor_props = util.preloaded.orm_descriptor_props
+
+        return self._filter_properties(descriptor_props.SynonymProperty)
 
     @_memoized_configured_property
     def column_attrs(self):
@@ -2399,6 +2408,7 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
         """
         return self._filter_properties(properties.ColumnProperty)
 
+    @util.preload_module("sqlalchemy.orm.relationships")
     @_memoized_configured_property
     def relationships(self):
         """A namespace of all :class:`.RelationshipProperty` properties
@@ -2422,9 +2432,12 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
             objects.
 
         """
-        return self._filter_properties(properties.RelationshipProperty)
+        return self._filter_properties(
+            util.preloaded.orm_relationships.RelationshipProperty
+        )
 
     @_memoized_configured_property
+    @util.preload_module("sqlalchemy.orm.descriptor_props")
     def composites(self):
         """Return a namespace of all :class:`.CompositeProperty`
         properties maintained by this :class:`.Mapper`.
@@ -2435,7 +2448,9 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
             objects.
 
         """
-        return self._filter_properties(properties.CompositeProperty)
+        return self._filter_properties(
+            util.preloaded.orm_descriptor_props.CompositeProperty
+        )
 
     def _filter_properties(self, type_):
         if Mapper._new_mappers:
@@ -2916,14 +2931,17 @@ class Mapper(sql_base.HasCacheKey, InspectionAttr):
 
         return None
 
-    @util.dependencies(
+    @util.preload_module(
         "sqlalchemy.ext.baked", "sqlalchemy.orm.strategy_options"
     )
-    def _subclass_load_via_in(self, baked, strategy_options, entity):
+    def _subclass_load_via_in(self, entity):
         """Assemble a BakedQuery that can load the columns local to
         this subclass as a SELECT with IN.
 
         """
+        strategy_options = util.preloaded.orm_strategy_options
+        baked = util.preloaded.ext_baked
+
         assert self.inherits
 
         polymorphic_prop = self._columntoproperty[self.polymorphic_on]
index 46c84d4bda826e5403f382aa5d4fbdcc9ad6f793..1460ae2089548f5a89845ccbba95349f4c14fbf2 100644 (file)
@@ -1710,8 +1710,9 @@ class BulkUD(object):
     def _do_before_compile(self):
         raise NotImplementedError()
 
-    @util.dependencies("sqlalchemy.orm.query")
-    def _do_pre(self, querylib):
+    @util.preload_module("sqlalchemy.orm.query")
+    def _do_pre(self):
+        querylib = util.preloaded.orm_query
         query = self.query
 
         self.context = querylib.QueryContext(query)
index 6ad8606e315555e91dbec2ea831370204e6a167d..aa9dd3274c90d71f081a29a97967bdb468ae0f46 100644 (file)
@@ -14,8 +14,13 @@ mapped attributes.
 from __future__ import absolute_import
 
 from . import attributes
+from .descriptor_props import ComparableProperty
+from .descriptor_props import CompositeProperty
+from .descriptor_props import ConcreteInheritedProperty
+from .descriptor_props import SynonymProperty
 from .interfaces import PropComparator
 from .interfaces import StrategizedProperty
+from .relationships import RelationshipProperty
 from .util import _orm_full_deannotate
 from .. import log
 from .. import util
@@ -23,7 +28,14 @@ from ..sql import coercions
 from ..sql import roles
 
 
-__all__ = ["ColumnProperty"]
+__all__ = [
+    "ColumnProperty",
+    "ComparableProperty",
+    "CompositeProperty",
+    "ConcreteInheritedProperty",
+    "RelationshipProperty",
+    "SynonymProperty",
+]
 
 
 @log.class_logger
@@ -190,16 +202,20 @@ class ColumnProperty(StrategizedProperty):
         if self.raiseload:
             self.strategy_key += (("raiseload", True),)
 
-    @util.dependencies("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
-    def _memoized_attr__deferred_column_loader(self, state, strategies):
+    @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
+    def _memoized_attr__deferred_column_loader(self):
+        state = util.preloaded.orm_state
+        strategies = util.preloaded.orm_strategies
         return state.InstanceState._instance_level_callable_processor(
             self.parent.class_manager,
             strategies.LoadDeferredColumns(self.key),
             self.key,
         )
 
-    @util.dependencies("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
-    def _memoized_attr__raise_column_loader(self, state, strategies):
+    @util.preload_module("sqlalchemy.orm.state", "sqlalchemy.orm.strategies")
+    def _memoized_attr__raise_column_loader(self):
+        state = util.preloaded.orm_state
+        strategies = util.preloaded.orm_strategies
         return state.InstanceState._instance_level_callable_processor(
             self.parent.class_manager,
             strategies.LoadDeferredColumns(self.key, True),
index 830cede93d5bf551fc09e1e2c06201e1d4de9021..796ebe3ac4edce750dacbcbab65ec4b780c6172f 100644 (file)
@@ -26,7 +26,6 @@ from . import exc as orm_exc
 from . import interfaces
 from . import loading
 from . import persistence
-from . import properties
 from .base import _assertions
 from .base import _entity_descriptor
 from .base import _is_aliased_class
@@ -1120,6 +1119,7 @@ class Query(Generative):
         """
         self._invoke_all_eagers = value
 
+    @util.preload_module("sqlalchemy.orm.relationships")
     def with_parent(self, instance, property=None, from_entity=None):  # noqa
         """Add filtering criterion that relates the given instance
         to a child object or collection, using its attribute state
@@ -1146,6 +1146,7 @@ class Query(Generative):
           "zero" entity of the :class:`.Query` itself.
 
         """
+        relationships = util.preloaded.orm_relationships
 
         if from_entity:
             entity_zero = inspect(from_entity)
@@ -1157,7 +1158,7 @@ class Query(Generative):
 
             for prop in mapper.iterate_properties:
                 if (
-                    isinstance(prop, properties.RelationshipProperty)
+                    isinstance(prop, relationships.RelationshipProperty)
                     and prop.mapper is entity_zero.mapper
                 ):
                     property = prop  # noqa
@@ -4089,7 +4090,7 @@ class Query(Generative):
 
 class LockmodeArg(ForUpdateArg):
     @classmethod
-    def parse_legacy_query(self, mode):
+    def parse_legacy_query(cls, mode):
         if mode in (None, False):
             return None
 
index 2995baf5fc6ecd1111b438fa4c8dfa6e3e1be8ec..f8437a00ffc36ca0cff8140a2bffd5679d20378b 100644 (file)
@@ -20,8 +20,6 @@ import re
 import weakref
 
 from . import attributes
-from . import dependency
-from . import mapper as mapperlib
 from .base import state_str
 from .interfaces import MANYTOMANY
 from .interfaces import MANYTOONE
@@ -96,7 +94,6 @@ def foreign(expr):
 
 
 @log.class_logger
-@util.langhelpers.dependency_for("sqlalchemy.orm.properties", add_to_all=True)
 class RelationshipProperty(StrategizedProperty):
     """Describes an object property that holds a single item or list
     of items that correspond to a related database table.
@@ -1514,7 +1511,9 @@ 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()
             return self.prop
@@ -1903,11 +1902,13 @@ class RelationshipProperty(StrategizedProperty):
             )
 
     @util.memoized_property
+    @util.preload_module("sqlalchemy.orm.mapper")
     def entity(self):  # type: () -> Union[AliasedInsp, mapperlib.Mapper]
         """Return the target mapped entity, which is an inspect() of the
         class or aliased class that is referred towards.
 
         """
+        mapperlib = util.preloaded.orm_mapper
         if callable(self.argument) and not isinstance(
             self.argument, (type, mapperlib.Mapper)
         ):
@@ -2045,10 +2046,11 @@ class RelationshipProperty(StrategizedProperty):
         self._calculated_foreign_keys = jc.foreign_key_columns
         self.secondary_synchronize_pairs = jc.secondary_synchronize_pairs
 
+    @util.preload_module("sqlalchemy.orm.mapper")
     def _check_conflicts(self):
         """Test that this relationship is legal, warn about
         inheritance conflicts."""
-
+        mapperlib = util.preloaded.orm_mapper
         if self.parent.non_primary and not mapperlib.class_mapper(
             self.parent.class_, configure=False
         ).has_property(self.key):
@@ -2233,7 +2235,10 @@ class RelationshipProperty(StrategizedProperty):
         if self.back_populates:
             self._add_reverse_property(self.back_populates)
 
+    @util.preload_module("sqlalchemy.orm.dependency")
     def _post_init(self):
+        dependency = util.preloaded.orm_dependency
+
         if self.uselist is None:
             self.uselist = self.direction is not MANYTOONE
         if not self.viewonly:
@@ -3132,7 +3137,9 @@ 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
 
index 74e5464835899fca7880f462f7019e43c5f81c34..b542f05065c64c32922c69ff007d360bdbb061af 100644 (file)
@@ -71,14 +71,14 @@ class _SessionClassMethods(object):
         close_all_sessions()
 
     @classmethod
-    @util.dependencies("sqlalchemy.orm.util")
-    def identity_key(cls, orm_util, *args, **kwargs):
+    @util.preload_module("sqlalchemy.orm.util")
+    def identity_key(cls, *args, **kwargs):
         """Return an identity key.
 
         This is an alias of :func:`.util.identity_key`.
 
         """
-        return orm_util.identity_key(*args, **kwargs)
+        return util.perload.orm_util.identity_key(*args, **kwargs)
 
     @classmethod
     def object_session(cls, instance):
index 41f878b7717cb6aba7e988c648bca899c24e3f64..91bf57ab920dae1f4843ab8619c899a371df5d73 100644 (file)
@@ -227,11 +227,11 @@ class InstanceState(interfaces.InspectionAttrInfo):
         return self.key is not None and not self._attached
 
     @property
-    @util.dependencies("sqlalchemy.orm.session")
-    def _attached(self, sessionlib):
+    @util.preload_module("sqlalchemy.orm.session")
+    def _attached(self):
         return (
             self.session_id is not None
-            and self.session_id in sessionlib._sessions
+            and self.session_id in util.preloaded.orm_session._sessions
         )
 
     def _track_last_known_value(self, key):
@@ -247,8 +247,8 @@ class InstanceState(interfaces.InspectionAttrInfo):
             self._last_known_values[key] = NO_VALUE
 
     @property
-    @util.dependencies("sqlalchemy.orm.session")
-    def session(self, sessionlib):
+    @util.preload_module("sqlalchemy.orm.session")
+    def session(self):
         """Return the owning :class:`.Session` for this instance,
         or ``None`` if none available.
 
@@ -260,7 +260,7 @@ class InstanceState(interfaces.InspectionAttrInfo):
         fully detached under normal circumstances.
 
         """
-        return sessionlib._state_session(self)
+        return util.preloaded.orm_session._state_session(self)
 
     @property
     def object(self):
index 9abb5be40883a1aa8f88cf90e911be5987484800..7d05a37f5c73751724b10563fe3f4908cb3b840f 100644 (file)
@@ -18,6 +18,7 @@ from . import interfaces
 from . import loading
 from . import properties
 from . import query
+from . import relationships
 from . import unitofwork
 from . import util as orm_util
 from .base import _DEFER_FOR_STATE
@@ -486,7 +487,7 @@ class AbstractRelationshipLoader(LoaderStrategy):
 
 
 @log.class_logger
-@properties.RelationshipProperty.strategy_for(do_nothing=True)
+@relationships.RelationshipProperty.strategy_for(do_nothing=True)
 class DoNothingLoader(LoaderStrategy):
     """Relationship loader that makes no change to the object's state.
 
@@ -498,8 +499,8 @@ class DoNothingLoader(LoaderStrategy):
 
 
 @log.class_logger
-@properties.RelationshipProperty.strategy_for(lazy="noload")
-@properties.RelationshipProperty.strategy_for(lazy=None)
+@relationships.RelationshipProperty.strategy_for(lazy="noload")
+@relationships.RelationshipProperty.strategy_for(lazy=None)
 class NoLoader(AbstractRelationshipLoader):
     """Provide loading behavior for a :class:`.RelationshipProperty`
     with "lazy=None".
@@ -531,11 +532,11 @@ class NoLoader(AbstractRelationshipLoader):
 
 
 @log.class_logger
-@properties.RelationshipProperty.strategy_for(lazy=True)
-@properties.RelationshipProperty.strategy_for(lazy="select")
-@properties.RelationshipProperty.strategy_for(lazy="raise")
-@properties.RelationshipProperty.strategy_for(lazy="raise_on_sql")
-@properties.RelationshipProperty.strategy_for(lazy="baked_select")
+@relationships.RelationshipProperty.strategy_for(lazy=True)
+@relationships.RelationshipProperty.strategy_for(lazy="select")
+@relationships.RelationshipProperty.strategy_for(lazy="raise")
+@relationships.RelationshipProperty.strategy_for(lazy="raise_on_sql")
+@relationships.RelationshipProperty.strategy_for(lazy="baked_select")
 class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
     """Provide loading behavior for a :class:`.RelationshipProperty`
     with "lazy=True", that is loads when first accessed.
@@ -799,14 +800,12 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             for pk in self.mapper.primary_key
         ]
 
-    @util.dependencies("sqlalchemy.ext.baked")
-    def _memoized_attr__bakery(self, baked):
-        return baked.bakery(size=50)
+    @util.preload_module("sqlalchemy.ext.baked")
+    def _memoized_attr__bakery(self):
+        return util.preloaded.ext_baked.bakery(size=50)
 
-    @util.dependencies("sqlalchemy.orm.strategy_options")
-    def _emit_lazyload(
-        self, strategy_options, session, state, primary_key_identity, passive
-    ):
+    @util.preload_module("sqlalchemy.orm.strategy_options")
+    def _emit_lazyload(self, session, state, primary_key_identity, passive):
         # emit lazy load now using BakedQuery, to cut way down on the overhead
         # of generating queries.
         # there are two big things we are trying to guard against here:
@@ -830,6 +829,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         # lazy loaders.   Currently the LRU cache is local to the LazyLoader,
         # however add ourselves to the initial cache key just to future
         # proof in case it moves
+        strategy_options = util.preloaded.orm_strategy_options
         q = self._bakery(lambda session: session.query(self.entity), self)
 
         q.add_criteria(
@@ -1008,7 +1008,7 @@ class PostLoader(AbstractRelationshipLoader):
         )
 
 
-@properties.RelationshipProperty.strategy_for(lazy="immediate")
+@relationships.RelationshipProperty.strategy_for(lazy="immediate")
 class ImmediateLoader(PostLoader):
     __slots__ = ()
 
@@ -1040,7 +1040,7 @@ class ImmediateLoader(PostLoader):
 
 
 @log.class_logger
-@properties.RelationshipProperty.strategy_for(lazy="subquery")
+@relationships.RelationshipProperty.strategy_for(lazy="subquery")
 class SubqueryLoader(PostLoader):
     __slots__ = ("join_depth",)
 
@@ -1492,8 +1492,8 @@ class SubqueryLoader(PostLoader):
 
 
 @log.class_logger
-@properties.RelationshipProperty.strategy_for(lazy="joined")
-@properties.RelationshipProperty.strategy_for(lazy=False)
+@relationships.RelationshipProperty.strategy_for(lazy="joined")
+@relationships.RelationshipProperty.strategy_for(lazy=False)
 class JoinedLoader(AbstractRelationshipLoader):
     """Provide loading behavior for a :class:`.RelationshipProperty`
     using joined eager loading.
@@ -2144,7 +2144,7 @@ class JoinedLoader(AbstractRelationshipLoader):
 
 
 @log.class_logger
-@properties.RelationshipProperty.strategy_for(lazy="selectin")
+@relationships.RelationshipProperty.strategy_for(lazy="selectin")
 class SelectInLoader(PostLoader, util.MemoizedSlots):
     __slots__ = (
         "join_depth",
@@ -2255,9 +2255,9 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
             (("lazy", "select"),)
         ).init_class_attribute(mapper)
 
-    @util.dependencies("sqlalchemy.ext.baked")
-    def _memoized_attr__bakery(self, baked):
-        return baked.bakery(size=50)
+    @util.preload_module("sqlalchemy.ext.baked")
+    def _memoized_attr__bakery(self):
+        return util.preloaded.ext_baked.bakery(size=50)
 
     def create_row_processor(
         self, context, path, loadopt, mapper, result, adapter, populators
@@ -2313,11 +2313,9 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
             effective_entity,
         )
 
-    @util.dependencies("sqlalchemy.ext.baked")
     def _load_for_path(
-        self, baked, context, path, states, load_only, effective_entity
+        self, context, path, states, load_only, effective_entity
     ):
-
         if load_only and self.key not in load_only:
             return
 
index 24e6fe205eb68983fe854f4089846d3611621996..5a3f99e700ad1ec4837d12a5ffb2ad6b265057b5 100644 (file)
@@ -15,7 +15,6 @@ organizes them in order of dependency, and executes.
 
 from . import attributes
 from . import exc as orm_exc
-from . import persistence
 from . import util as orm_util
 from .. import event
 from .. import util
@@ -568,7 +567,9 @@ class PostUpdateAll(PostSortRec):
         self.mapper = mapper
         self.isdelete = isdelete
 
+    @util.preload_module("sqlalchemy.orm.persistence")
     def execute(self, uow):
+        persistence = util.preloaded.orm_persistence
         states, cols = uow.post_update_states[self.mapper]
         states = [s for s in states if uow.states[s][0] == self.isdelete]
 
@@ -582,8 +583,9 @@ class SaveUpdateAll(PostSortRec):
         self.mapper = mapper
         assert mapper is mapper.base_mapper
 
+    @util.preload_module("sqlalchemy.orm.persistence")
     def execute(self, uow):
-        persistence.save_obj(
+        util.preloaded.orm_persistence.save_obj(
             self.mapper,
             uow.states_for_mapper_hierarchy(self.mapper, False, False),
             uow,
@@ -617,8 +619,9 @@ class DeleteAll(PostSortRec):
         self.mapper = mapper
         assert mapper is mapper.base_mapper
 
+    @util.preload_module("sqlalchemy.orm.persistence")
     def execute(self, uow):
-        persistence.delete_obj(
+        util.preloaded.orm_persistence.delete_obj(
             self.mapper,
             uow.states_for_mapper_hierarchy(self.mapper, True, False),
             uow,
@@ -687,7 +690,9 @@ class SaveUpdateState(PostSortRec):
         self.state = state
         self.mapper = state.mapper.base_mapper
 
+    @util.preload_module("sqlalchemy.orm.persistence")
     def execute_aggregate(self, uow, recs):
+        persistence = util.preloaded.orm_persistence
         cls_ = self.__class__
         mapper = self.mapper
         our_recs = [
@@ -712,7 +717,9 @@ class DeleteState(PostSortRec):
         self.state = state
         self.mapper = state.mapper.base_mapper
 
+    @util.preload_module("sqlalchemy.orm.persistence")
     def execute_aggregate(self, uow, recs):
+        persistence = util.preloaded.orm_persistence
         cls_ = self.__class__
         mapper = self.mapper
         our_recs = [
index 488717041dadb38b06317d41d6893b4785deac75..281b7d0f24e02e4a43337b373008e8aec55c162c 100644 (file)
@@ -120,7 +120,7 @@ def __go(lcls):
     _prepare_annotations(FromClause, AnnotatedFromClause)
     _prepare_annotations(ClauseList, Annotated)
 
-    _sa_util.dependencies.resolve_all("sqlalchemy.sql")
+    _sa_util.preloaded.import_prefix("sqlalchemy.sql")
 
     from . import naming  # noqa
 
index 89839ea28da0124d7e45786e367e851793b9c62b..b61c7dc5e1b8c2fa6f69acb1cd4dfcde893aaaef 100644 (file)
@@ -209,6 +209,14 @@ class _DialectArgDict(util.collections_abc.MutableMapping):
         del self._non_defaults[key]
 
 
+@util.preload_module("sqlalchemy.dialects")
+def _kw_reg_for_dialect(dialect_name):
+    dialect_cls = util.preloaded.dialects.registry.load(dialect_name)
+    if dialect_cls.construct_arguments is None:
+        return None
+    return dict(dialect_cls.construct_arguments)
+
+
 class DialectKWArgs(object):
     """Establish the ability for a class to have dialect-specific arguments
     with defaults and constructor validation.
@@ -307,13 +315,6 @@ class DialectKWArgs(object):
         """A synonym for :attr:`.DialectKWArgs.dialect_kwargs`."""
         return self.dialect_kwargs
 
-    @util.dependencies("sqlalchemy.dialects")
-    def _kw_reg_for_dialect(dialects, dialect_name):
-        dialect_cls = dialects.registry.load(dialect_name)
-        if dialect_cls.construct_arguments is None:
-            return None
-        return dict(dialect_cls.construct_arguments)
-
     _kw_registry = util.PopulateDict(_kw_reg_for_dialect)
 
     def _kw_reg_for_dialect_cls(self, dialect_name):
index 3ebcf24b03e2a910e0e775566bdac6e0a3390787..b37c462160e59cac167a59a80407b3b1d4c1727a 100644 (file)
@@ -1022,9 +1022,10 @@ class SQLCompiler(Compiled):
 
         return expanded_state
 
-    @util.dependencies("sqlalchemy.engine.result")
-    def _create_result_map(self, result):
+    @util.preload_module("sqlalchemy.engine.result")
+    def _create_result_map(self):
         """utility method used for unit tests only."""
+        result = util.preloaded.engine_result
         return result.CursorResultMetaData._create_description_match_map(
             self._result_columns
         )
@@ -4127,8 +4128,10 @@ class IdentifierPreparer(object):
             ident = self.quote_identifier(ident)
         return ident
 
-    @util.dependencies("sqlalchemy.sql.naming")
-    def format_constraint(self, naming, constraint, _alembic_quote=True):
+    @util.preload_module("sqlalchemy.sql.naming")
+    def format_constraint(self, constraint, _alembic_quote=True):
+        naming = util.preloaded.sql_naming
+
         if isinstance(constraint.name, elements._defer_name):
             name = naming._constraint_name_for_table(
                 constraint, constraint.table
index 47739a37df14ea920daea31f54d1be9431e771b6..bb68e8a7e2284fe2d56fb11eccfae2a1e3d9f7c5 100644 (file)
@@ -422,8 +422,8 @@ class ClauseElement(
 
         return self
 
-    @util.dependencies("sqlalchemy.engine.default")
-    def compile(self, default, bind=None, dialect=None, **kw):
+    @util.preload_module("sqlalchemy.engine.default")
+    def compile(self, bind=None, dialect=None, **kw):
         """Compile this SQL expression.
 
         The return value is a :class:`~.Compiled` object.
@@ -477,6 +477,7 @@ class ClauseElement(
 
         """
 
+        default = util.preloaded.engine_default
         if not dialect:
             if bind:
                 dialect = bind.dialect
@@ -1782,8 +1783,8 @@ class TextClause(
             else:
                 new_params[key] = existing._with_value(value)
 
-    @util.dependencies("sqlalchemy.sql.selectable")
-    def columns(self, selectable, *cols, **types):
+    @util.preload_module("sqlalchemy.sql.selectable")
+    def columns(self, *cols, **types):
         r"""Turn this :class:`.TextClause` object into a
         :class:`.TextualSelect` object that serves the same role as a SELECT
         statement.
@@ -1888,6 +1889,7 @@ class TextClause(
          argument as it also indicates positional ordering.
 
         """
+        selectable = util.preloaded.sql_selectable
         positional_input_cols = [
             ColumnClause(col.key, types.pop(col.key))
             if col.key in types
index 4c627c4cc624f64b0581cb199b14ffb026923fba..69f60ba246e6ef0bc46488d8528d077bf865fdda 100644 (file)
@@ -2193,8 +2193,10 @@ class ColumnDefault(DefaultGenerator):
         )
 
     @util.memoized_property
-    @util.dependencies("sqlalchemy.sql.sqltypes")
-    def _arg_is_typed(self, sqltypes):
+    @util.preload_module("sqlalchemy.sql.sqltypes")
+    def _arg_is_typed(self):
+        sqltypes = util.preloaded.sql_sqltypes
+
         if self.is_clause_element:
             return not isinstance(self.arg.type, sqltypes.NullType)
         else:
@@ -2440,14 +2442,16 @@ class Sequence(roles.StatementRole, DefaultGenerator):
     def is_clause_element(self):
         return False
 
-    @util.dependencies("sqlalchemy.sql.functions.func")
-    def next_value(self, func):
+    @util.preload_module("sqlalchemy.sql.functions")
+    def next_value(self):
         """Return a :class:`.next_value` function element
         which will render the appropriate increment function
         for this :class:`.Sequence` within any SQL expression.
 
         """
-        return func.next_value(self, bind=self.bind)
+        return util.preloaded.sql_functions.func.next_value(
+            self, bind=self.bind
+        )
 
     def _set_parent(self, column):
         super(Sequence, self)._set_parent(column)
@@ -3925,10 +3929,10 @@ class MetaData(SchemaItem):
         """
         return self._bind
 
-    @util.dependencies("sqlalchemy.engine.url")
-    def _bind_to(self, url, bind):
+    @util.preload_module("sqlalchemy.engine.url")
+    def _bind_to(self, bind):
         """Bind this MetaData to an Engine, Connection, string or URL."""
-
+        url = util.preloaded.engine_url
         if isinstance(bind, util.string_types + (url.URL,)):
             self._bind = sqlalchemy.create_engine(bind)
         else:
@@ -4231,10 +4235,10 @@ class ThreadLocalMetaData(MetaData):
 
         return getattr(self.context, "_engine", None)
 
-    @util.dependencies("sqlalchemy.engine.url")
-    def _bind_to(self, url, bind):
+    @util.preload_module("sqlalchemy.engine.url")
+    def _bind_to(self, bind):
         """Bind to a Connectable in the caller's thread."""
-
+        url = util.preloaded.engine_url
         if isinstance(bind, util.string_types + (url.URL,)):
             try:
                 self.context._engine = self.__engines[bind]
index 965ac6e7fed5c2b89610312c28d40cd590a8f811..5536b27bc4c3f25d74fffaf4b17572652c3b11d6 100644 (file)
@@ -164,14 +164,13 @@ class Selectable(ReturnsRows):
         "deprecated, and will be removed in a future release.  Similar "
         "functionality is available via the sqlalchemy.sql.visitors module.",
     )
-    @util.dependencies("sqlalchemy.sql.util")
-    def replace_selectable(self, sqlutil, old, alias):
+    @util.preload_module("sqlalchemy.sql.util")
+    def replace_selectable(self, old, alias):
         """replace all occurrences of FromClause 'old' with the given Alias
         object, returning a copy of this :class:`.FromClause`.
 
         """
-
-        return sqlutil.ClauseAdapter(alias).traverse(self)
+        return util.preloaded.sql_util.ClauseAdapter(alias).traverse(self)
 
     def corresponding_column(self, column, require_embedded=False):
         """Given a :class:`.ColumnElement`, return the exported
@@ -358,8 +357,8 @@ class FromClause(HasMemoized, roles.AnonymizedFromClauseRole, Selectable):
         ":class:`.functions.count` function available from the "
         ":attr:`.func` namespace.",
     )
-    @util.dependencies("sqlalchemy.sql.functions")
-    def count(self, functions, whereclause=None, **params):
+    @util.preload_module("sqlalchemy.sql.functions")
+    def count(self, whereclause=None, **params):
         """return a SELECT COUNT generated against this
         :class:`.FromClause`.
 
@@ -368,7 +367,7 @@ class FromClause(HasMemoized, roles.AnonymizedFromClauseRole, Selectable):
             :class:`.functions.count`
 
         """
-
+        functions = util.preloaded.sql_functions
         if self.primary_key:
             col = list(self.primary_key)[0]
         else:
@@ -801,8 +800,9 @@ class Join(FromClause):
     def self_group(self, against=None):
         return FromGrouping(self)
 
-    @util.dependencies("sqlalchemy.sql.util")
-    def _populate_column_collection(self, sqlutil):
+    @util.preload_module("sqlalchemy.sql.util")
+    def _populate_column_collection(self):
+        sqlutil = util.preloaded.sql_util
         columns = [c for c in self.left.columns] + [
             c for c in self.right.columns
         ]
@@ -1033,8 +1033,8 @@ class Join(FromClause):
     def bind(self):
         return self.left.bind or self.right.bind
 
-    @util.dependencies("sqlalchemy.sql.util")
-    def alias(self, sqlutil, name=None, flat=False):
+    @util.preload_module("sqlalchemy.sql.util")
+    def alias(self, name=None, flat=False):
         r"""return an alias of this :class:`.Join`.
 
         The default behavior here is to first produce a SELECT
@@ -1134,6 +1134,7 @@ class Join(FromClause):
             :func:`~.expression.alias`
 
         """
+        sqlutil = util.preloaded.sql_util
         if flat:
             assert name is None, "Can't send name argument with flat"
             left_a, right_a = (
@@ -1458,8 +1459,9 @@ class TableSample(AliasedReturnsRows):
         self.seed = seed
         super(TableSample, self)._init(selectable, name=name)
 
-    @util.dependencies("sqlalchemy.sql.functions")
-    def _get_method(self, functions):
+    @util.preload_module("sqlalchemy.sql.functions")
+    def _get_method(self):
+        functions = util.preloaded.sql_functions
         if isinstance(self.sampling, functions.Function):
             return self.sampling
         else:
@@ -1929,8 +1931,8 @@ class TableClause(Immutable, FromClause):
         self._columns.add(c)
         c.table = self
 
-    @util.dependencies("sqlalchemy.sql.dml")
-    def insert(self, dml, values=None, inline=False, **kwargs):
+    @util.preload_module("sqlalchemy.sql.dml")
+    def insert(self, values=None, inline=False, **kwargs):
         """Generate an :func:`.insert` construct against this
         :class:`.TableClause`.
 
@@ -1941,13 +1943,12 @@ class TableClause(Immutable, FromClause):
         See :func:`.insert` for argument and usage information.
 
         """
+        return util.preloaded.sql_dml.Insert(
+            self, values=values, inline=inline, **kwargs
+        )
 
-        return dml.Insert(self, values=values, inline=inline, **kwargs)
-
-    @util.dependencies("sqlalchemy.sql.dml")
-    def update(
-        self, dml, whereclause=None, values=None, inline=False, **kwargs
-    ):
+    @util.preload_module("sqlalchemy.sql.dml")
+    def update(self, whereclause=None, values=None, inline=False, **kwargs):
         """Generate an :func:`.update` construct against this
         :class:`.TableClause`.
 
@@ -1958,8 +1959,7 @@ class TableClause(Immutable, FromClause):
         See :func:`.update` for argument and usage information.
 
         """
-
-        return dml.Update(
+        return util.preloaded.sql_dml.Update(
             self,
             whereclause=whereclause,
             values=values,
@@ -1967,8 +1967,8 @@ class TableClause(Immutable, FromClause):
             **kwargs
         )
 
-    @util.dependencies("sqlalchemy.sql.dml")
-    def delete(self, dml, whereclause=None, **kwargs):
+    @util.preload_module("sqlalchemy.sql.dml")
+    def delete(self, whereclause=None, **kwargs):
         """Generate a :func:`.delete` construct against this
         :class:`.TableClause`.
 
@@ -1979,8 +1979,7 @@ class TableClause(Immutable, FromClause):
         See :func:`.delete` for argument and usage information.
 
         """
-
-        return dml.Delete(self, whereclause, **kwargs)
+        return util.preloaded.sql_dml.Delete(self, whereclause, **kwargs)
 
     @property
     def _from_objects(self):
@@ -3864,8 +3863,8 @@ class Select(
         """
         return self.add_columns(column)
 
-    @util.dependencies("sqlalchemy.sql.util")
-    def reduce_columns(self, sqlutil, only_synonyms=True):
+    @util.preload_module("sqlalchemy.sql.util")
+    def reduce_columns(self, only_synonyms=True):
         """Return a new :func`.select` construct with redundantly
         named, equivalently-valued columns removed from the columns clause.
 
@@ -3887,7 +3886,7 @@ class Select(
 
         """
         return self.with_only_columns(
-            sqlutil.reduce_columns(
+            util.preloaded.sql_util.reduce_columns(
                 self.inner_columns,
                 only_synonyms=only_synonyms,
                 *(self._whereclause,) + tuple(self._from_obj)
index 2d6b4429914a516e082da82a662a9ff65977dc18..3d69d1177258ca94198cd9bd57152408dad2f005 100644 (file)
@@ -1539,8 +1539,9 @@ class Enum(Emulated, String, SchemaType):
             not self.native_enum or not compiler.dialect.supports_native_enum
         )
 
-    @util.dependencies("sqlalchemy.sql.schema")
-    def _set_table(self, schema, column, table):
+    @util.preload_module("sqlalchemy.sql.schema")
+    def _set_table(self, column, table):
+        schema = util.preloaded.sql_schema
         SchemaType._set_table(self, column, table)
 
         if not self.create_constraint:
@@ -1738,8 +1739,9 @@ class Boolean(Emulated, TypeEngine, SchemaType):
             and compiler.dialect.non_native_boolean_check_constraint
         )
 
-    @util.dependencies("sqlalchemy.sql.schema")
-    def _set_table(self, schema, column, table):
+    @util.preload_module("sqlalchemy.sql.schema")
+    def _set_table(self, column, table):
+        schema = util.preloaded.sql_schema
         if not self.create_constraint:
             return
 
@@ -2228,8 +2230,7 @@ class JSON(Indexable, TypeEngine):
     class Comparator(Indexable.Comparator, Concatenable.Comparator):
         """Define comparison operations for :class:`.types.JSON`."""
 
-        @util.dependencies("sqlalchemy.sql.default_comparator")
-        def _setup_getitem(self, default_comparator, index):
+        def _setup_getitem(self, index):
             if not isinstance(index, util.string_types) and isinstance(
                 index, compat.collections_abc.Sequence
             ):
@@ -2553,8 +2554,8 @@ class ARRAY(SchemaEventTarget, Indexable, Concatenable, TypeEngine):
                 "ARRAY type; please use the dialect-specific ARRAY type"
             )
 
-        @util.dependencies("sqlalchemy.sql.elements")
-        def any(self, elements, other, operator=None):
+        @util.preload_module("sqlalchemy.sql.elements")
+        def any(self, other, operator=None):
             """Return ``other operator ANY (array)`` clause.
 
             Argument places are switched, because ANY requires array
@@ -2582,14 +2583,15 @@ class ARRAY(SchemaEventTarget, Indexable, Concatenable, TypeEngine):
                 :meth:`.types.ARRAY.Comparator.all`
 
             """
+            elements = util.preloaded.sql_elements
             operator = operator if operator else operators.eq
             return operator(
                 coercions.expect(roles.ExpressionElementRole, other),
                 elements.CollectionAggregate._create_any(self.expr),
             )
 
-        @util.dependencies("sqlalchemy.sql.elements")
-        def all(self, elements, other, operator=None):
+        @util.preload_module("sqlalchemy.sql.elements")
+        def all(self, other, operator=None):
             """Return ``other operator ALL (array)`` clause.
 
             Argument places are switched, because ALL requires array
@@ -2617,6 +2619,7 @@ class ARRAY(SchemaEventTarget, Indexable, Concatenable, TypeEngine):
                 :meth:`.types.ARRAY.Comparator.any`
 
             """
+            elements = util.preloaded.sql_elements
             operator = operator if operator else operators.eq
             return operator(
                 coercions.expect(roles.ExpressionElementRole, other),
index c29a04ee0966ee6d80cf812d5bab4b7cb3e4df17..2a4f7ebb6881bdc69d4ae94d16ab3e7de873086b 100644 (file)
@@ -597,9 +597,9 @@ class _GetChildren(InternalTraversal):
 _get_children = _GetChildren()
 
 
-@util.dependencies("sqlalchemy.sql.elements")
-def _resolve_name_for_compare(elements, element, name, anon_map, **kw):
-    if isinstance(name, elements._anonymous_label):
+@util.preload_module("sqlalchemy.sql.elements")
+def _resolve_name_for_compare(element, name, anon_map, **kw):
+    if isinstance(name, util.preloaded.sql_elements._anonymous_label):
         name = name.apply_map(anon_map)
 
     return name
index 739f9619549c91d25327a48f02a089caedacaba2..38189ec9d5b217ef97e0503a44bae693706efe5e 100644 (file)
@@ -64,13 +64,15 @@ class TypeEngine(Traversible):
             self.expr = expr
             self.type = expr.type
 
-        @util.dependencies("sqlalchemy.sql.default_comparator")
-        def operate(self, default_comparator, op, *other, **kwargs):
+        @util.preload_module("sqlalchemy.sql.default_comparator")
+        def operate(self, op, *other, **kwargs):
+            default_comparator = util.preloaded.sql_default_comparator
             o = default_comparator.operator_lookup[op.__name__]
             return o[0](self.expr, op, *(other + o[1:]), **kwargs)
 
-        @util.dependencies("sqlalchemy.sql.default_comparator")
-        def reverse_operate(self, default_comparator, op, other, **kwargs):
+        @util.preload_module("sqlalchemy.sql.default_comparator")
+        def reverse_operate(self, op, other, **kwargs):
+            default_comparator = util.preloaded.sql_default_comparator
             o = default_comparator.operator_lookup[op.__name__]
             return o[0](self.expr, op, other, reverse=True, *o[1:], **kwargs)
 
@@ -613,8 +615,9 @@ class TypeEngine(Traversible):
 
         return dialect.type_compiler.process(self)
 
-    @util.dependencies("sqlalchemy.engine.default")
-    def _default_dialect(self, default):
+    @util.preload_module("sqlalchemy.engine.default")
+    def _default_dialect(self):
+        default = util.preloaded.engine_default
         if self.__class__.__module__.startswith("sqlalchemy.dialects"):
             tokens = self.__class__.__module__.split(".")[0:3]
             mod = ".".join(tokens)
index 819d18018905585ccb821636d15eab1da3d3298c..1909619c59bb86263b6dbc142615c651bda75ade 100644 (file)
@@ -110,7 +110,6 @@ from .langhelpers import constructor_key  # noqa
 from .langhelpers import counter  # noqa
 from .langhelpers import decode_slice  # noqa
 from .langhelpers import decorator  # noqa
-from .langhelpers import dependencies  # noqa
 from .langhelpers import dictlike_iteritems  # noqa
 from .langhelpers import duck_type_collection  # noqa
 from .langhelpers import ellipses_string  # noqa
@@ -137,6 +136,8 @@ from .langhelpers import NoneType  # noqa
 from .langhelpers import only_once  # noqa
 from .langhelpers import PluginLoader  # noqa
 from .langhelpers import portable_instancemethod  # noqa
+from .langhelpers import preloaded  # noqa
+from .langhelpers import preload_module  # noqa
 from .langhelpers import quoted_token_parser  # noqa
 from .langhelpers import safe_reraise  # noqa
 from .langhelpers import set_creation_order  # noqa
index 09aa94bf2bcfd22d137861ebe44ea9bfe6853f88..bac95c7e3ec7a868e8186862ad3f7dbf39a79142 100644 (file)
@@ -987,135 +987,54 @@ class MemoizedSlots(object):
             return self._fallback_getattr(key)
 
 
-def dependency_for(modulename, add_to_all=False):
-    def decorate(obj):
-        tokens = modulename.split(".")
-        mod = compat.import_(
-            ".".join(tokens[0:-1]), globals(), locals(), [tokens[-1]]
-        )
-        mod = getattr(mod, tokens[-1])
-        setattr(mod, obj.__name__, obj)
-        if add_to_all and hasattr(mod, "__all__"):
-            mod.__all__.append(obj.__name__)
-        return obj
-
-    return decorate
-
-
-class dependencies(object):
-    """Apply imported dependencies as arguments to a function.
-
-    E.g.::
-
-        @util.dependencies(
-            "sqlalchemy.sql.widget",
-            "sqlalchemy.engine.default"
-        );
-        def some_func(self, widget, default, arg1, arg2, **kw):
-            # ...
-
-    Rationale is so that the impact of a dependency cycle can be
-    associated directly with the few functions that cause the cycle,
-    and not pollute the module-level namespace.
-
+class _ModuleRegistry:
+    """Registry of modules to load in a package init file.
+
+    To avoid potential thread safety issues for imports that are deferred
+    in a function, like https://bugs.python.org/issue38884, these modules
+    are added to the system module cache by importing them after the packages
+    has finished initialization.
+
+    A global instance is provided under the name :attr:`.preloaded`. Use
+    the function :func:`.preload_module` to register modules to load and
+    :meth:`.import_prefix` to load all the modules that start with the
+    given path.
+
+    While the modules are loaded in the global module cache, it's advisable
+    to access them using :attr:`.preloaded` to ensure that it was actually
+    registered. Each registered module is added to the instance ``__dict__``
+    in the form `<package>_<module>`, omitting ``sqlalchemy`` from the package
+    name. Example: ``sqlalchemy.sql.util`` becomes ``preloaded.sql_util``.
     """
 
-    def __init__(self, *deps):
-        self.import_deps = []
-        for dep in deps:
-            tokens = dep.split(".")
-            self.import_deps.append(
-                dependencies._importlater(".".join(tokens[0:-1]), tokens[-1])
-            )
-
-    def __call__(self, fn):
-        import_deps = self.import_deps
-        spec = compat.inspect_getfullargspec(fn)
-
-        spec_zero = list(spec[0])
-        hasself = spec_zero[0] in ("self", "cls")
-
-        for i in range(len(import_deps)):
-            spec[0][i + (1 if hasself else 0)] = "import_deps[%r]" % i
-
-        inner_spec = format_argspec_plus(spec, grouped=False)
+    def __init__(self, prefix="sqlalchemy"):
+        self.module_registry = set()
 
-        for impname in import_deps:
-            del spec_zero[1 if hasself else 0]
-        spec[0][:] = spec_zero
+    def preload_module(self, *deps):
+        """Adds the specified modules to the list to load.
 
-        outer_spec = format_argspec_plus(spec, grouped=False)
-
-        code = "lambda %(args)s: fn(%(apply_kw)s)" % {
-            "args": outer_spec["args"],
-            "apply_kw": inner_spec["apply_kw"],
-        }
-
-        decorated = eval(code, locals())
-        decorated.__defaults__ = getattr(fn, "im_func", fn).__defaults__
-        return update_wrapper(decorated, fn)
-
-    @classmethod
-    def resolve_all(cls, path):
-        for m in list(dependencies._unresolved):
-            if m._full_path.startswith(path):
-                m._resolve()
-
-    _unresolved = set()
-    _by_key = {}
-
-    class _importlater(object):
-        _unresolved = set()
-
-        _by_key = {}
+        This method can be used both as a normal function and as a decorator.
+        No change is performed to the decorated object.
+        """
+        self.module_registry.update(deps)
+        return lambda fn: fn
 
-        def __new__(cls, path, addtl):
-            key = path + "." + addtl
-            if key in dependencies._by_key:
-                return dependencies._by_key[key]
-            else:
-                dependencies._by_key[key] = imp = object.__new__(cls)
-                return imp
-
-        def __init__(self, path, addtl):
-            self._il_path = path
-            self._il_addtl = addtl
-            dependencies._unresolved.add(self)
-
-        @property
-        def _full_path(self):
-            return self._il_path + "." + self._il_addtl
-
-        @memoized_property
-        def module(self):
-            if self in dependencies._unresolved:
-                raise ImportError(
-                    "importlater.resolve_all() hasn't "
-                    "been called (this is %s %s)"
-                    % (self._il_path, self._il_addtl)
+    def import_prefix(self, path):
+        """Resolve all the modules in the registry that start with the
+        specified path.
+        """
+        for module in self.module_registry:
+            key = module.split("sqlalchemy.")[-1].replace(".", "_")
+            if module.startswith(path) and key not in self.__dict__:
+                tokens = module.split(".")
+                compat.import_(
+                    ".".join(tokens[0:-1]), globals(), locals(), [tokens[-1]]
                 )
+                self.__dict__[key] = sys.modules[module]
 
-            return getattr(self._initial_import, self._il_addtl)
 
-        def _resolve(self):
-            dependencies._unresolved.discard(self)
-            self._initial_import = compat.import_(
-                self._il_path, globals(), locals(), [self._il_addtl]
-            )
-
-        def __getattr__(self, key):
-            if key == "module":
-                raise ImportError(
-                    "Could not resolve module %s" % self._full_path
-                )
-            try:
-                attr = getattr(self.module, key)
-            except AttributeError:
-                raise AttributeError(
-                    "Module %s has no attribute '%s'" % (self._full_path, key)
-                )
-            self.__dict__[key] = attr
-            return attr
+preloaded = _ModuleRegistry()
+preload_module = preloaded.preload_module
 
 
 # from paste.deploy.converters
index 183e157e5e6997368ce98f039fbb6677d4b0435b..4392df0137e4027a5f7663bd1e16993e89974ff4 100644 (file)
@@ -3,6 +3,7 @@
 import copy
 import datetime
 import inspect
+import sys
 
 from sqlalchemy import exc
 from sqlalchemy import sql
@@ -3209,3 +3210,35 @@ class TimezoneTest(fixtures.TestBase):
             repr(timezone(datetime.timedelta(hours=5))),
             "sqlalchemy.util.timezone(%r)" % (datetime.timedelta(hours=5)),
         )
+
+
+class TestModuleRegistry(fixtures.TestBase):
+    def test_modules_are_loaded(self):
+        to_restore = []
+        for m in ("xml.dom", "wsgiref.simple_server"):
+            to_restore.append((m, sys.modules.pop(m, None)))
+        try:
+            mr = langhelpers._ModuleRegistry()
+
+            ret = mr.preload_module(
+                "xml.dom", "wsgiref.simple_server", "sqlalchemy.sql.util"
+            )
+            o = object()
+            is_(ret(o), o)
+
+            is_false(hasattr(mr, "xml_dom"))
+            mr.import_prefix("xml")
+            is_true("xml.dom" in sys.modules)
+            is_(sys.modules["xml.dom"], mr.xml_dom)
+
+            is_true("wsgiref.simple_server" not in sys.modules)
+            mr.import_prefix("wsgiref")
+            is_true("wsgiref.simple_server" in sys.modules)
+            is_(sys.modules["wsgiref.simple_server"], mr.wsgiref_simple_server)
+
+            mr.import_prefix("sqlalchemy")
+            is_(sys.modules["sqlalchemy.sql.util"], mr.sql_util)
+        finally:
+            for name, mod in to_restore:
+                if mod is not None:
+                    sys.modules[name] = mod
index 935e6f43119036fe74b52f46094ebc21e146a8c0..d81b99ccd5a9339f7e8ca48bba9c9fe74b1da7d0 100644 (file)
@@ -26,10 +26,10 @@ from sqlalchemy.orm import composite
 from sqlalchemy.orm import configure_mappers
 from sqlalchemy.orm import create_session
 from sqlalchemy.orm import deferred
+from sqlalchemy.orm import descriptor_props
 from sqlalchemy.orm import exc as orm_exc
 from sqlalchemy.orm import joinedload
 from sqlalchemy.orm import mapper
-from sqlalchemy.orm import properties
 from sqlalchemy.orm import relationship
 from sqlalchemy.orm import Session
 from sqlalchemy.orm.events import MapperEvents
@@ -1144,7 +1144,7 @@ class DeclarativeTest(DeclarativeTestBase):
 
         assert ASub.brap.property is A.data.property
         assert isinstance(
-            ASub.brap.original_property, properties.SynonymProperty
+            ASub.brap.original_property, descriptor_props.SynonymProperty
         )
 
     def test_alt_name_attr_subclass_relationship_inline(self):
@@ -1166,7 +1166,7 @@ class DeclarativeTest(DeclarativeTestBase):
 
         assert ASub.brap.property is A.b.property
         assert isinstance(
-            ASub.brap.original_property, properties.SynonymProperty
+            ASub.brap.original_property, descriptor_props.SynonymProperty
         )
         ASub(brap=B())
 
@@ -1179,7 +1179,9 @@ class DeclarativeTest(DeclarativeTestBase):
 
         A.brap = A.data
         assert A.brap.property is A.data.property
-        assert isinstance(A.brap.original_property, properties.SynonymProperty)
+        assert isinstance(
+            A.brap.original_property, descriptor_props.SynonymProperty
+        )
 
     def test_alt_name_attr_subclass_relationship_attrset(self):
         # [ticket:2900]
@@ -1196,7 +1198,9 @@ class DeclarativeTest(DeclarativeTestBase):
             id = Column("id", Integer, primary_key=True)
 
         assert A.brap.property is A.b.property
-        assert isinstance(A.brap.original_property, properties.SynonymProperty)
+        assert isinstance(
+            A.brap.original_property, descriptor_props.SynonymProperty
+        )
         A(brap=B())
 
     def test_eager_order_by(self):
index 29b64547ba5ae1a70a14b6dd2979ceba33fa196e..99e7358e5ae5d1c09f544e3a0c2d8cf465d1d930 100644 (file)
@@ -2704,7 +2704,7 @@ class ComparatorFactoryTest(_fixtures.FixtureTest, AssertsCompiledSQL):
             self.classes.User,
         )
 
-        from sqlalchemy.orm.properties import RelationshipProperty
+        from sqlalchemy.orm.relationships import RelationshipProperty
 
         # NOTE: this API changed in 0.8, previously __clause_element__()
         # gave the parent selecatable, now it gives the
index be9aa2d584d0043b6e760b25fd210cf7df3a0803..55223dc9a9053e7d6ecc7eeedec0189082819fbd 100644 (file)
@@ -225,17 +225,17 @@ test.aaa_profiling.test_orm.AnnotatedOverheadTest.test_no_entity_wo_annotations
 
 # TEST: test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set
 
-test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set 2.7_sqlite_pysqlite_dbapiunicode_cextensions 3812
-test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 3812
-test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set 3.7_sqlite_pysqlite_dbapiunicode_cextensions 3933
-test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 3933
+test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set 2.7_sqlite_pysqlite_dbapiunicode_cextensions 3567
+test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 3567
+test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set 3.7_sqlite_pysqlite_dbapiunicode_cextensions 3688
+test.aaa_profiling.test_orm.AttributeOverheadTest.test_attribute_set 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 3688
 
 # TEST: test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove
 
-test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove 2.7_sqlite_pysqlite_dbapiunicode_cextensions 6026
-test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 6026
-test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove 3.7_sqlite_pysqlite_dbapiunicode_cextensions 6228
-test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 6228
+test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove 2.7_sqlite_pysqlite_dbapiunicode_cextensions 5626
+test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove 2.7_sqlite_pysqlite_dbapiunicode_nocextensions 5626
+test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove 3.7_sqlite_pysqlite_dbapiunicode_cextensions 5828
+test.aaa_profiling.test_orm.AttributeOverheadTest.test_collection_append_remove 3.7_sqlite_pysqlite_dbapiunicode_nocextensions 5828
 
 # TEST: test.aaa_profiling.test_orm.BranchedOptionTest.test_generate_path_cache_key_bound_branching
 
index a70eea1370c1f2e5614b9828f80d78003cb9c13d..19a8c54cc5f900b2ed1e6a44ecb8d664050a73d2 100644 (file)
@@ -9,4 +9,4 @@ class DeprecationWarningsTest(fixtures.TestBase):
         with expect_deprecated_20(
             "The `database` package is deprecated and will be removed in v2.0 "
         ):
-            import_('sqlalchemy.databases')
+            import_("sqlalchemy.databases")