diff options
| author | mike bayer <mike_mp@zzzcomputing.com> | 2020-03-10 21:26:37 +0000 |
|---|---|---|
| committer | Gerrit Code Review <gerrit@bbpush.zzzcomputing.com> | 2020-03-10 21:26:37 +0000 |
| commit | 99864e0c2e3ccb0de22e74312f97e97b1495747c (patch) | |
| tree | 10350f1e78f34da518c607099e536d4341fa6353 /lib/sqlalchemy | |
| parent | 71e368081a63b323bea092b379523d8cd0ab3fdb (diff) | |
| parent | eda6dbbf387def2063d1b6719b64b20f9e7f2ab4 (diff) | |
| download | sqlalchemy-99864e0c2e3ccb0de22e74312f97e97b1495747c.tar.gz | |
Merge "Simplified module pre-loading strategy and made it linter friendly"
Diffstat (limited to 'lib/sqlalchemy')
31 files changed, 281 insertions, 289 deletions
diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index 2d67db24c..0f18aba33 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -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()) diff --git a/lib/sqlalchemy/ext/__init__.py b/lib/sqlalchemy/ext/__init__.py index 247301b94..1f842fc2a 100644 --- a/lib/sqlalchemy/ext/__init__.py +++ b/lib/sqlalchemy/ext/__init__.py @@ -8,4 +8,4 @@ from .. import util as _sa_util -_sa_util.dependencies.resolve_all("sqlalchemy.ext") +_sa_util.preloaded.import_prefix("sqlalchemy.ext") diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py index c8695b7bf..ca7d3a022 100644 --- a/lib/sqlalchemy/ext/declarative/api.py +++ b/lib/sqlalchemy/ext/declarative/api.py @@ -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): diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py index 51ba35b4b..314e96cf1 100644 --- a/lib/sqlalchemy/ext/declarative/base.py +++ b/lib/sqlalchemy/ext/declarative/base.py @@ -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 diff --git a/lib/sqlalchemy/ext/declarative/clsregistry.py b/lib/sqlalchemy/ext/declarative/clsregistry.py index 93e643cf5..71594aae7 100644 --- a/lib/sqlalchemy/ext/declarative/clsregistry.py +++ b/lib/sqlalchemy/ext/declarative/clsregistry.py @@ -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 diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 8f6e3c218..f644e0451 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -278,8 +278,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()) diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index bebf72a9d..c2067c228 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -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 " diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index a8d2731db..2a3ef54dd 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -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 diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py index f0db1d86f..4a50f6464 100644 --- a/lib/sqlalchemy/orm/events.py +++ b/lib/sqlalchemy/orm/events.py @@ -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): diff --git a/lib/sqlalchemy/orm/exc.py b/lib/sqlalchemy/orm/exc.py index f009b9c0b..0c2565487 100644 --- a/lib/sqlalchemy/orm/exc.py +++ b/lib/sqlalchemy/orm/exc.py @@ -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: diff --git a/lib/sqlalchemy/orm/loading.py b/lib/sqlalchemy/orm/loading.py index d943ebb19..c3d4773cd 100644 --- a/lib/sqlalchemy/orm/loading.py +++ b/lib/sqlalchemy/orm/loading.py @@ -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: diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 91e3251e2..5619d2e16 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -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] diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index 46c84d4bd..1460ae208 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -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) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 6ad8606e3..aa9dd3274 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -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), diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 830cede93..796ebe3ac 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -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 diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index 2995baf5f..f8437a00f 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -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 diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 74e546483..b542f0506 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -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): diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 41f878b77..91bf57ab9 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -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): diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 9abb5be40..7d05a37f5 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -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 diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index 24e6fe205..5a3f99e70 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -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 = [ diff --git a/lib/sqlalchemy/sql/__init__.py b/lib/sqlalchemy/sql/__init__.py index 488717041..281b7d0f2 100644 --- a/lib/sqlalchemy/sql/__init__.py +++ b/lib/sqlalchemy/sql/__init__.py @@ -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 diff --git a/lib/sqlalchemy/sql/base.py b/lib/sqlalchemy/sql/base.py index 89839ea28..b61c7dc5e 100644 --- a/lib/sqlalchemy/sql/base.py +++ b/lib/sqlalchemy/sql/base.py @@ -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): diff --git a/lib/sqlalchemy/sql/compiler.py b/lib/sqlalchemy/sql/compiler.py index 3ebcf24b0..b37c46216 100644 --- a/lib/sqlalchemy/sql/compiler.py +++ b/lib/sqlalchemy/sql/compiler.py @@ -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 diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 47739a37d..bb68e8a7e 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -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 diff --git a/lib/sqlalchemy/sql/schema.py b/lib/sqlalchemy/sql/schema.py index 4c627c4cc..69f60ba24 100644 --- a/lib/sqlalchemy/sql/schema.py +++ b/lib/sqlalchemy/sql/schema.py @@ -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] diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 965ac6e7f..5536b27bc 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -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) diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py index 2d6b44299..3d69d1177 100644 --- a/lib/sqlalchemy/sql/sqltypes.py +++ b/lib/sqlalchemy/sql/sqltypes.py @@ -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), diff --git a/lib/sqlalchemy/sql/traversals.py b/lib/sqlalchemy/sql/traversals.py index c29a04ee0..2a4f7ebb6 100644 --- a/lib/sqlalchemy/sql/traversals.py +++ b/lib/sqlalchemy/sql/traversals.py @@ -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 diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py index 739f96195..38189ec9d 100644 --- a/lib/sqlalchemy/sql/type_api.py +++ b/lib/sqlalchemy/sql/type_api.py @@ -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) diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py index 819d18018..1909619c5 100644 --- a/lib/sqlalchemy/util/__init__.py +++ b/lib/sqlalchemy/util/__init__.py @@ -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 diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index 09aa94bf2..bac95c7e3 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -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 |
