summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2008-02-11 19:22:34 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2008-02-11 19:22:34 +0000
commit6f9aa3a9003d4d63348bc56f612690a153da640c (patch)
tree13c41e15a1d80de7236a9b834c6cc25ea9b23eed /lib
parent645fa5255d899431d489b89d1c567bce96c1bb4d (diff)
downloadsqlalchemy-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.py15
-rw-r--r--lib/sqlalchemy/orm/mapper.py35
-rw-r--r--lib/sqlalchemy/orm/query.py7
-rw-r--r--lib/sqlalchemy/orm/session.py16
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.