diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-12-12 13:01:34 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-12-12 13:01:34 -0500 |
| commit | 0d71ea8126137d2b3d4141aa0fb30c2e64376d44 (patch) | |
| tree | edcc99227a474a61686f277fdc36cf2b0960e14c /lib/sqlalchemy | |
| parent | 6d5dd2214a4cc6340d8f07147a43fac03a12b040 (diff) | |
| download | sqlalchemy-0d71ea8126137d2b3d4141aa0fb30c2e64376d44.tar.gz | |
- inlinings and callcount reductions
- add test coverage for the rare case of noload->lazyload + pickle
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/orm/__init__.py | 29 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 28 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/dependency.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/dynamic.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 8 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/state.py | 12 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 62 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/unitofwork.py | 6 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/util.py | 4 | ||||
| -rw-r--r-- | lib/sqlalchemy/util/__init__.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/util/compat.py | 10 |
11 files changed, 101 insertions, 64 deletions
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 0e9e6739c..63331e081 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -1018,7 +1018,7 @@ def clear_mappers(): def joinedload(*keys, **kw): """Return a ``MapperOption`` that will convert the property of the given - name into an joined eager load. + name or series of mapped attributes into an joined eager load. .. note:: This function is known as :func:`eagerload` in all versions of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4 @@ -1065,7 +1065,8 @@ def joinedload(*keys, **kw): def joinedload_all(*keys, **kw): """Return a ``MapperOption`` that will convert all properties along the - given dot-separated path into an joined eager load. + given dot-separated path or series of mapped attributes + into an joined eager load. .. note:: This function is known as :func:`eagerload_all` in all versions of SQLAlchemy prior to version 0.6beta3, including the 0.5 and 0.4 @@ -1111,7 +1112,8 @@ def eagerload_all(*args, **kwargs): def subqueryload(*keys): """Return a ``MapperOption`` that will convert the property - of the given name into an subquery eager load. + of the given name or series of mapped attributes + into an subquery eager load. Used with :meth:`~sqlalchemy.orm.query.Query.options`. @@ -1135,7 +1137,8 @@ def subqueryload(*keys): def subqueryload_all(*keys): """Return a ``MapperOption`` that will convert all properties along the - given dot-separated path into a subquery eager load. + given dot-separated path or series of mapped attributes + into a subquery eager load. Used with :meth:`~sqlalchemy.orm.query.Query.options`. @@ -1158,7 +1161,7 @@ def subqueryload_all(*keys): def lazyload(*keys): """Return a ``MapperOption`` that will convert the property of the given - name into a lazy load. + name or series of mapped attributes into a lazy load. Used with :meth:`~sqlalchemy.orm.query.Query.options`. @@ -1167,9 +1170,21 @@ def lazyload(*keys): """ return strategies.EagerLazyOption(keys, lazy=True) +def lazyload_all(*keys): + """Return a ``MapperOption`` that will convert all the properties + along the given dot-separated path or series of mapped attributes + into a lazy load. + + Used with :meth:`~sqlalchemy.orm.query.Query.options`. + + See also: :func:`eagerload`, :func:`subqueryload`, :func:`immediateload` + + """ + return strategies.EagerLazyOption(keys, lazy=True, chained=True) + def noload(*keys): """Return a ``MapperOption`` that will convert the property of the - given name into a non-load. + given name or series of mapped attributes into a non-load. Used with :meth:`~sqlalchemy.orm.query.Query.options`. @@ -1180,7 +1195,7 @@ def noload(*keys): def immediateload(*keys): """Return a ``MapperOption`` that will convert the property of the given - name into an immediate load. + name or series of mapped attributes into an immediate load. Used with :meth:`~sqlalchemy.orm.query.Query.options`. diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 4d0379503..002215268 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -109,7 +109,7 @@ class QueryableAttribute(interfaces.PropComparator): def __str__(self): return repr(self.parententity) + "." + self.property.key - @property + @util.memoized_property def property(self): return self.comparator.property @@ -310,14 +310,6 @@ class AttributeImpl(object): def get_all_pending(self, state, dict_): raise NotImplementedError() - def _get_callable(self, state): - if self.key in state.callables: - return state.callables[self.key] - elif self.callable_ is not None: - return self.callable_(state) - else: - return None - def initialize(self, state, dict_): """Initialize the given state's attribute with an empty value.""" @@ -340,7 +332,13 @@ class AttributeImpl(object): if passive is PASSIVE_NO_INITIALIZE: return PASSIVE_NO_RESULT - callable_ = self._get_callable(state) + if self.key in state.callables: + callable_ = state.callables[self.key] + elif self.callable_ is not None: + callable_ = self.callable_(state) + else: + callable_ = None + if callable_ is not None: #if passive is not PASSIVE_OFF: # return PASSIVE_NO_RESULT @@ -370,21 +368,19 @@ class AttributeImpl(object): """return the unchanged value of this attribute""" if self.key in state.committed_state: - if state.committed_state[self.key] is NO_VALUE: + value = state.committed_state[self.key] + if value is NO_VALUE: return None else: - return state.committed_state.get(self.key) + return value else: return self.get(state, dict_, passive=passive) def set_committed_value(self, state, dict_, value): """set an attribute value on the given instance and 'commit' it.""" + dict_[self.key] = value state.commit(dict_, [self.key]) - - state.callables.pop(self.key, None) - state.dict[self.key] = value - return value class ScalarAttributeImpl(AttributeImpl): diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index b9958f775..19c78c5c8 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -844,7 +844,7 @@ class DetectKeySwitch(DependencyProcessor): uowcommit, self.passive_updates) def _pks_changed(self, uowcommit, state): - return state.has_identity and sync.source_modified(uowcommit, + return bool(state.key) and sync.source_modified(uowcommit, state, self.mapper, self.prop.synchronize_pairs) diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py index 710b710a9..4637bad7e 100644 --- a/lib/sqlalchemy/orm/dynamic.py +++ b/lib/sqlalchemy/orm/dynamic.py @@ -199,7 +199,7 @@ class AppenderMixin(object): self.attr = attr mapper = object_mapper(instance) - prop = mapper.get_property(self.attr.key) + prop = mapper._props[self.attr.key] self._criterion = prop.compare( operators.eq, instance, diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index cd9f01f38..dc0799d14 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -872,7 +872,7 @@ class Mapper(object): for mapper in self.iterate_to_root(): for (key, cls) in mapper.delete_orphans: if attributes.manager_of_class(cls).has_parent( - state, key, optimistic=state.has_identity): + state, key, optimistic=bool(state.key)): return False o = o or bool(mapper.delete_orphans) return o @@ -1582,7 +1582,7 @@ class Mapper(object): else: conn = connection - has_identity = state.has_identity + has_identity = bool(state.key) mapper = _state_mapper(state) instance_key = state.key or mapper._identity_key_from_state(state) @@ -1998,7 +1998,7 @@ class Mapper(object): tups.append((state, state.dict, _state_mapper(state), - state.has_identity, + bool(state.key), conn)) table_to_mapper = self._sorted_tables @@ -2503,7 +2503,7 @@ def _load_scalar_attributes(state, attribute_names): "attribute refresh operation cannot proceed" % (state_str(state))) - has_key = state.has_identity + has_key = bool(state.key) result = False diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index 278f86749..48861085d 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -375,10 +375,14 @@ class InstanceState(object): """ class_manager = self.manager - for key in keys: - if key in dict_ and key in class_manager.mutable_attributes: - self.committed_state[key] = self.manager[key].impl.copy(dict_[key]) - else: + if class_manager.mutable_attributes: + for key in keys: + if key in dict_ and key in class_manager.mutable_attributes: + self.committed_state[key] = self.manager[key].impl.copy(dict_[key]) + else: + self.committed_state.pop(key, None) + else: + for key in keys: self.committed_state.pop(key, None) self.expired = False diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index a6711ae26..d6fb0c005 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -245,7 +245,7 @@ class DeferredColumnLoader(LoaderStrategy): path, adapter, **kwargs) def _class_level_loader(self, state): - if not state.has_identity: + if not state.key: return None return LoadDeferredColumns(state, self.key) @@ -255,19 +255,28 @@ log.class_logger(DeferredColumnLoader) class LoadDeferredColumns(object): """serializable loader object used by DeferredColumnLoader""" + + __slots__ = 'state', 'key' def __init__(self, state, key): - self.state, self.key = state, key - + self.state = state + self.key = key + + def __getstate__(self): + return self.state, self.key + + def __setstate__(self, state): + self.state, self.key = state + def __call__(self, passive=False): + state, key = self.state, self.key + if passive is attributes.PASSIVE_NO_FETCH: return attributes.PASSIVE_NO_RESULT - state = self.state - localparent = mapper._state_mapper(state) - prop = localparent.get_property(self.key) + prop = localparent._props[key] strategy = prop._get_strategy(DeferredColumnLoader) if strategy.group: @@ -279,7 +288,7 @@ class LoadDeferredColumns(object): p.group==strategy.group ] else: - toload = [self.key] + toload = [key] # narrow the keys down to just those which have no history group = [k for k in toload if k in state.unmodified] @@ -289,7 +298,7 @@ class LoadDeferredColumns(object): raise orm_exc.DetachedInstanceError( "Parent instance %s is not bound to a Session; " "deferred load operation of attribute '%s' cannot proceed" % - (mapperutil.state_str(state), self.key) + (mapperutil.state_str(state), key) ) query = session.query(localparent) @@ -475,7 +484,7 @@ class LazyLoader(AbstractRelationshipLoader): return criterion def _class_level_loader(self, state): - if not state.has_identity and \ + if not state.key and \ (not self.parent_property.load_on_pending or not state.session_id): return None @@ -556,20 +565,23 @@ log.class_logger(LazyLoader) class LoadLazyAttribute(object): """serializable loader object used by LazyLoader""" - + + __slots__ = 'state', 'key' + def __init__(self, state, key): - self.state, self.key = state, key - + self.state = state + self.key = key + def __getstate__(self): - return (self.state, self.key) - + return self.state, self.key + def __setstate__(self, state): self.state, self.key = state - + def __call__(self, passive=False): - state = self.state + state, key = self.state, self.key instance_mapper = mapper._state_mapper(state) - prop = instance_mapper.get_property(self.key) + prop = instance_mapper._props[key] strategy = prop._get_strategy(LazyLoader) pending = not state.key @@ -587,7 +599,7 @@ class LoadLazyAttribute(object): raise orm_exc.DetachedInstanceError( "Parent instance %s is not bound to a Session; " "lazy load operation of attribute '%s' cannot proceed" % - (mapperutil.state_str(state), self.key) + (mapperutil.state_str(state), key) ) # if we have a simple primary key load, check the @@ -612,8 +624,8 @@ class LoadLazyAttribute(object): if _none_set.issuperset(ident): return None - key = prop.mapper.identity_key_from_primary_key(ident) - instance = Query._get_from_identity(session, key, passive) + ident_key = prop.mapper.identity_key_from_primary_key(ident) + instance = Query._get_from_identity(session, ident_key, passive) if instance is not None: return instance elif passive is attributes.PASSIVE_NO_FETCH: @@ -626,13 +638,13 @@ class LoadLazyAttribute(object): q = q.autoflush(False) if state.load_path: - q = q._with_current_path(state.load_path + (self.key,)) + q = q._with_current_path(state.load_path + (key,)) if state.load_options: q = q._conditional_options(*state.load_options) if strategy.use_get: - return q._load_on_ident(key) + return q._load_on_ident(ident_key) if prop.order_by: q = q.order_by(*util.to_list(prop.order_by)) @@ -736,7 +748,7 @@ class SubqueryLoader(AbstractRelationshipLoader): else: leftmost_mapper, leftmost_prop = \ subq_mapper, \ - subq_mapper.get_property(subq_path[1]) + subq_mapper._props[subq_path[1]] leftmost_cols, remote_cols = self._local_remote_columns(leftmost_prop) leftmost_attr = [ @@ -1272,7 +1284,7 @@ class LoadEagerFromAliasOption(PropertyOption): if isinstance(self.alias, basestring): mapper = mappers[-1] (root_mapper, propname) = paths[-1][-2:] - prop = mapper.get_property(propname) + prop = mapper._props[propname] self.alias = prop.target.alias(self.alias) query._attributes[ ("user_defined_eager_row_processor", @@ -1281,7 +1293,7 @@ class LoadEagerFromAliasOption(PropertyOption): else: (root_mapper, propname) = paths[-1][-2:] mapper = mappers[-1] - prop = mapper.get_property(propname) + prop = mapper._props[propname] adapter = query._polymorphic_adapters.get(prop.mapper, None) query._attributes[ ("user_defined_eager_row_processor", diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index d9d64fe39..ab62e5324 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -35,7 +35,7 @@ class UOWEventHandler(interfaces.AttributeExtension): sess = session._state_session(state) if sess: - prop = _state_mapper(state).get_property(self.key) + prop = _state_mapper(state)._props[self.key] if prop.cascade.save_update and \ (prop.cascade_backrefs or self.key == initiator.key) and \ item not in sess: @@ -45,7 +45,7 @@ class UOWEventHandler(interfaces.AttributeExtension): def remove(self, state, item, initiator): sess = session._state_session(state) if sess: - prop = _state_mapper(state).get_property(self.key) + prop = _state_mapper(state)._props[self.key] # expunge pending orphans if prop.cascade.delete_orphan and \ item in sess.new and \ @@ -60,7 +60,7 @@ class UOWEventHandler(interfaces.AttributeExtension): sess = session._state_session(state) if sess: - prop = _state_mapper(state).get_property(self.key) + prop = _state_mapper(state)._props[self.key] if newvalue is not None and \ prop.cascade.save_update and \ (prop.cascade_backrefs or self.key == initiator.key) and \ diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index c59dbed69..b5fa0c0cf 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -12,6 +12,7 @@ from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE,\ PropComparator, MapperProperty,\ AttributeExtension from sqlalchemy.orm import attributes, exc +import operator mapperlib = util.importlater("sqlalchemy.orm", "mapperlib") @@ -514,8 +515,7 @@ def _attr_as_key(attr): def _is_aliased_class(entity): return isinstance(entity, AliasedClass) -def _state_mapper(state): - return state.manager.mapper +_state_mapper = util.dottedgetter('manager.mapper') def object_mapper(instance): """Given an object, return the primary Mapper associated with the object diff --git a/lib/sqlalchemy/util/__init__.py b/lib/sqlalchemy/util/__init__.py index aa150874f..9119e35b7 100644 --- a/lib/sqlalchemy/util/__init__.py +++ b/lib/sqlalchemy/util/__init__.py @@ -6,7 +6,7 @@ from compat import callable, cmp, reduce, defaultdict, py25_dict, \ threading, py3k, jython, win32, set_types, buffer, pickle, \ - update_wrapper, partial, md5_hex, decode_slice + update_wrapper, partial, md5_hex, decode_slice, dottedgetter from _collections import NamedTuple, ImmutableContainer, frozendict, \ Properties, OrderedProperties, ImmutableProperties, OrderedDict, \ diff --git a/lib/sqlalchemy/util/compat.py b/lib/sqlalchemy/util/compat.py index 59dd9eaf0..79dd6228f 100644 --- a/lib/sqlalchemy/util/compat.py +++ b/lib/sqlalchemy/util/compat.py @@ -188,6 +188,16 @@ else: def decode_slice(slc): return (slc.start, slc.stop, slc.step) +if sys.version_info >= (2, 6): + from operator import attrgetter as dottedgetter +else: + def dottedgetter(attr): + def g(obj): + for name in attr.split("."): + obj = getattr(obj, name) + return obj + return g + import decimal |
