diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-02-11 19:22:34 +0000 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2008-02-11 19:22:34 +0000 |
| commit | 6f9aa3a9003d4d63348bc56f612690a153da640c (patch) | |
| tree | 13c41e15a1d80de7236a9b834c6cc25ea9b23eed /lib | |
| parent | 645fa5255d899431d489b89d1c567bce96c1bb4d (diff) | |
| download | sqlalchemy-6f9aa3a9003d4d63348bc56f612690a153da640c.tar.gz | |
- added expire_all() method to Session. Calls expire()
for all persistent instances. This is handy in conjunction
with .....
- instances which have been partially or fully expired
will have their expired attributes populated during a regular
Query operation which affects those objects, preventing
a needless second SQL statement for each instance.
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 15 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 35 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 7 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/session.py | 16 |
4 files changed, 50 insertions, 23 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index e08a1a0c2..5ae79e432 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -775,7 +775,14 @@ class InstanceState(object): serializable. """ instance = self.obj() - self.class_._class_state.deferred_scalar_loader(instance, [k for k in self.expired_attributes if k in self.unmodified]) + + unmodified = self.unmodified + self.class_._class_state.deferred_scalar_loader(instance, [ + attr.impl.key for attr in _managed_attributes(self.class_) if + attr.impl.accepts_scalar_loader and + attr.impl.key in self.expired_attributes and + attr.impl.key in unmodified + ]) for k in self.expired_attributes: self.callables.pop(k, None) self.expired_attributes.clear() @@ -798,20 +805,18 @@ class InstanceState(object): if attribute_names is None: for attr in _managed_attributes(self.class_): self.dict.pop(attr.impl.key, None) - + self.expired_attributes.add(attr.impl.key) if attr.impl.accepts_scalar_loader: self.callables[attr.impl.key] = self - self.expired_attributes.add(attr.impl.key) self.committed_state = {} else: for key in attribute_names: self.dict.pop(key, None) self.committed_state.pop(key, None) - + self.expired_attributes.add(key) if getattr(self.class_, key).impl.accepts_scalar_loader: self.callables[key] = self - self.expired_attributes.add(key) def reset(self, key): """remove the given attribute and any callables associated with it.""" diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index d78973e94..85aec2f44 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -1371,13 +1371,22 @@ class Mapper(object): if 'populate_instance' not in extension.methods or extension.populate_instance(self, context, row, instance, only_load_props=only_load_props, instancekey=identitykey, isnew=isnew) is EXT_CONTINUE: self.populate_instance(context, instance, row, only_load_props=only_load_props, instancekey=identitykey, isnew=isnew) + + else: + attrs = getattr(state, 'expired_attributes', None) + # populate attributes on non-loading instances which have been expired + # TODO: also support deferred attributes here [ticket:870] + if attrs: + if state in context.partials: + isnew = False + attrs = context.partials[state] + else: + isnew = True + attrs = state.expired_attributes.intersection(state.unmodified) + context.partials[state] = attrs #<-- allow query.instances to commit the subset of attrs -# NOTYET: populate attributes on non-loading instances which have been expired, deferred, etc. -# elif getattr(state, 'expired_attributes', None): # TODO: base off total set of unloaded attributes, not just exp -# attrs = state.expired_attributes.intersection(state.unmodified) -# if 'populate_instance' not in extension.methods or extension.populate_instance(self, context, row, instance, only_load_props=attrs, instancekey=identitykey, isnew=isnew) is EXT_CONTINUE: -# self.populate_instance(context, instance, row, only_load_props=attrs, instancekey=identitykey, isnew=isnew) -# context.partials.add((state, attrs)) <-- allow query.instances to commit the subset of attrs + if 'populate_instance' not in extension.methods or extension.populate_instance(self, context, row, instance, only_load_props=attrs, instancekey=identitykey, isnew=isnew) is EXT_CONTINUE: + self.populate_instance(context, instance, row, only_load_props=attrs, instancekey=identitykey, isnew=isnew) if result is not None and ('append_result' not in extension.methods or extension.append_result(self, context, row, instance, result, instancekey=identitykey, isnew=isnew) is EXT_CONTINUE): result.append(instance) @@ -1448,10 +1457,10 @@ class Mapper(object): if self.non_primary: selectcontext.attributes[('populating_mapper', instance._state)] = self - def _post_instance(self, selectcontext, state): + def _post_instance(self, selectcontext, state, **kwargs): post_processors = selectcontext.attributes[('post_processors', self, None)] for p in post_processors: - p(state.obj()) + p(state.obj(), **kwargs) def _get_poly_select_loader(self, selectcontext, row): """set up attribute loaders for 'select' and 'deferred' polymorphic loading. @@ -1475,11 +1484,13 @@ class Mapper(object): identitykey = self.identity_key_from_instance(instance) + only_load_props = flags.get('only_load_props', None) + params = {} for c, bind in param_names: params[bind] = self._get_attr_by_column(instance, c) row = selectcontext.session.connection(self).execute(statement, params).fetchone() - self.populate_instance(selectcontext, instance, row, isnew=False, instancekey=identitykey, ispostselect=True) + self.populate_instance(selectcontext, instance, row, isnew=False, instancekey=identitykey, ispostselect=True, only_load_props=only_load_props) return post_execute elif hosted_mapper.polymorphic_fetch == 'deferred': from sqlalchemy.orm.strategies import DeferredColumnLoader @@ -1494,6 +1505,12 @@ class Mapper(object): props = [prop for prop in [self._get_col_to_prop(col) for col in statement.inner_columns] if prop.key not in instance.__dict__] keys = [p.key for p in props] + + only_load_props = flags.get('only_load_props', None) + if only_load_props: + keys = util.Set(keys).difference(only_load_props) + props = [p for p in props if p.key in only_load_props] + for prop in props: strategy = prop._get_strategy(DeferredColumnLoader) instance._state.set_callable(prop.key, strategy.setup_loader(instance, props=keys, create_statement=create_statement)) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 35f632f1e..2bb87ea71 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -903,6 +903,7 @@ class Query(object): while True: context.progress = util.Set() + context.partials = {} if self._yield_per: fetch = cursor.fetchmany(self._yield_per) @@ -927,7 +928,11 @@ class Query(object): for ii in context.progress: context.attributes.get(('populating_mapper', ii), _state_mapper(ii))._post_instance(context, ii) ii.commit_all() - + + for ii, attrs in context.partials.items(): + context.attributes.get(('populating_mapper', ii), _state_mapper(ii))._post_instance(context, ii, only_load_props=attrs) + ii.commit(attrs) + for row in rows: yield row diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index c75b78664..8f85a496c 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -820,7 +820,14 @@ class Session(object): if self.query(_object_mapper(instance))._get(instance._instance_key, refresh_instance=instance._state, only_load_props=attribute_names) is None: raise exceptions.InvalidRequestError("Could not refresh instance '%s'" % mapperutil.instance_str(instance)) - + + def expire_all(self): + """Expires all persistent instances within this Session. + + """ + for state in self.identity_map.all_states(): + _expire_state(state, None) + def expire(self, instance, attribute_names=None): """Expire the attributes on the given instance. @@ -829,13 +836,6 @@ class Session(object): to the database which will refresh all attributes with their current value. - Lazy-loaded relational attributes will remain lazily loaded, so that - triggering one will incur the instance-wide refresh operation, followed - immediately by the lazy load of that attribute. - - Eagerly-loaded relational attributes will eagerly load within the - single refresh operation. - The ``attribute_names`` argument is an iterable collection of attribute names indicating a subset of attributes to be expired. |
