summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/interfaces.py13
-rw-r--r--lib/sqlalchemy/orm/mapper.py39
-rw-r--r--lib/sqlalchemy/orm/query.py2
-rw-r--r--lib/sqlalchemy/orm/session.py46
-rw-r--r--lib/sqlalchemy/orm/strategies.py92
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py7
6 files changed, 102 insertions, 97 deletions
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py
index ec180d74a..c1933d3ff 100644
--- a/lib/sqlalchemy/orm/interfaces.py
+++ b/lib/sqlalchemy/orm/interfaces.py
@@ -258,20 +258,25 @@ class MapperProperty(object):
callables are of the following form::
- def execute(instance, row, **flags):
- # process incoming instance and given row.
+ def new_execute(instance, row, **flags):
+ # process incoming instance and given row. the instance is "new" and
+ # was just created upon receipt of this row.
# flags is a dictionary containing at least the following attributes:
# isnew - indicates if the instance was newly created as a result of reading this row
# instancekey - identity key of the instance
# optional attribute:
# ispostselect - indicates if this row resulted from a 'post' select of additional tables/columns
+
+ def existing_execute(instance, row, **flags):
+ # process incoming instance and given row. the instance is "existing" and
+ # was created based on a previous row.
def post_execute(instance, **flags):
# process instance after all result rows have been processed. this
# function should be used to issue additional selections in order to
# eagerly load additional properties.
- return (execute, post_execute)
+ return (new_execute, existing_execute, post_execute)
either tuple value can also be ``None`` in which case no function is called.
@@ -528,7 +533,7 @@ class SynonymProperty(MapperProperty):
pass
def create_row_processor(self, selectcontext, mapper, row):
- return (None, None)
+ return (None, None, None)
def do_init(self):
if not self.proxy:
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index 5f60d26e0..960282255 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -1396,7 +1396,7 @@ class Mapper(object):
identitykey = self.identity_key_from_row(row)
populate_existing = context.populate_existing or self.always_refresh
if identitykey in context.session.identity_map:
- instance = context.session._get(identitykey)
+ instance = context.session.identity_map[identitykey]
if self.__should_log_debug:
self.__log_debug("_instance(): using existing instance %s identity %s" % (mapperutil.instance_str(instance), str(identitykey)))
isnew = False
@@ -1454,6 +1454,9 @@ class Mapper(object):
if extension.append_result(self, context, row, instance, result, **flags) is EXT_CONTINUE:
if result is not None:
result.append(instance)
+
+ instance._instance_key = identitykey
+
return instance
def _create_instance(self, session):
@@ -1495,7 +1498,7 @@ class Mapper(object):
newrow[c] = row[c2]
return newrow
- def populate_instance(self, selectcontext, instance, row, ispostselect=None, **flags):
+ def populate_instance(self, selectcontext, instance, row, ispostselect=None, isnew=False, **flags):
"""populate an instance from a result row."""
selectcontext.stack.push_mapper(self)
@@ -1504,17 +1507,21 @@ class Mapper(object):
# "snapshot" of the stack, which represents a path from the lead mapper in the query to this one,
# including relation() names. the key also includes "self", and allows us to distinguish between
# other mappers within our inheritance hierarchy
- populators = selectcontext.attributes.get(('instance_populators', self, selectcontext.stack.snapshot(), ispostselect), None)
+ snapshot = selectcontext.stack.snapshot()
+ populators = selectcontext.attributes.get(((isnew or ispostselect) and 'new_populators' or 'existing_populators', self, snapshot, ispostselect), None)
if populators is None:
# no populators; therefore this is the first time we are receiving a row for
# this result set. issue create_row_processor() on all MapperProperty objects
# and cache in the select context.
- populators = []
+ new_populators = []
+ existing_populators = []
post_processors = []
for prop in self.__props.values():
- (pop, post_proc) = prop.create_row_processor(selectcontext, self, row)
- if pop is not None:
- populators.append(pop)
+ (newpop, existingpop, post_proc) = prop.create_row_processor(selectcontext, self, row)
+ if newpop is not None:
+ new_populators.append(newpop)
+ if existingpop is not None:
+ existing_populators.append(existingpop)
if post_proc is not None:
post_processors.append(post_proc)
@@ -1522,11 +1529,16 @@ class Mapper(object):
if poly_select_loader is not None:
post_processors.append(poly_select_loader)
- selectcontext.attributes[('instance_populators', self, selectcontext.stack.snapshot(), ispostselect)] = populators
+ selectcontext.attributes[('new_populators', self, snapshot, ispostselect)] = new_populators
+ selectcontext.attributes[('existing_populators', self, snapshot, ispostselect)] = existing_populators
selectcontext.attributes[('post_processors', self, ispostselect)] = post_processors
-
+ if isnew or ispostselect:
+ populators = new_populators
+ else:
+ populators = existing_populators
+
for p in populators:
- p(instance, row, ispostselect=ispostselect, **flags)
+ p(instance, row, ispostselect=ispostselect, isnew=isnew, **flags)
selectcontext.stack.pop()
@@ -1570,9 +1582,10 @@ class ClassKey(object):
def __init__(self, class_, entity_name):
self.class_ = class_
self.entity_name = entity_name
-
+ self._hash = hash((self.class_, self.entity_name))
+
def __hash__(self):
- return hash((self.class_, self.entity_name))
+ return self._hash
def __eq__(self, other):
return self is other
@@ -1615,7 +1628,7 @@ def object_mapper(object, entity_name=None, raiseerror=True):
raise exceptions.InvalidRequestError("Class '%s' entity name '%s' has no mapper associated with it" % (object.__class__.__name__, getattr(object, '_entity_name', entity_name)))
else:
return None
- return mapper.compile()
+ return mapper
def class_mapper(class_, entity_name=None, compile=True):
"""Given a class and optional entity_name, return the primary Mapper associated with the key.
diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py
index 5cbe19ce2..cb30eafcf 100644
--- a/lib/sqlalchemy/orm/query.py
+++ b/lib/sqlalchemy/orm/query.py
@@ -698,7 +698,7 @@ class Query(object):
lockmode = lockmode or self._lockmode
if not reload and not self.mapper.always_refresh and lockmode is None:
try:
- return self.session._get(key)
+ return self.session.identity_map[key]
except KeyError:
pass
diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py
index c6922b928..ebd4bd3d3 100644
--- a/lib/sqlalchemy/orm/session.py
+++ b/lib/sqlalchemy/orm/session.py
@@ -933,14 +933,14 @@ class Session(object):
return object_session(obj)
object_session = classmethod(object_session)
- def _save_impl(self, object, **kwargs):
- if hasattr(object, '_instance_key'):
- if object._instance_key not in self.identity_map:
+ def _save_impl(self, obj, **kwargs):
+ if hasattr(obj, '_instance_key'):
+ if obj._instance_key not in self.identity_map:
raise exceptions.InvalidRequestError("Instance '%s' is a detached instance "
"or is already persistent in a "
- "different Session" % repr(object))
+ "different Session" % repr(obj))
else:
- m = _class_mapper(object.__class__, entity_name=kwargs.get('entity_name', None))
+ m = _class_mapper(obj.__class__, entity_name=kwargs.get('entity_name', None))
# this would be a nice exception to raise...however this is incompatible with a contextual
# session which puts all objects into the session upon construction.
@@ -949,31 +949,23 @@ class Session(object):
# "and must be attached to a parent "
# "object to be saved" % (repr(object)))
- m._assign_entity_name(object)
- self._register_pending(object)
+ m._assign_entity_name(obj)
+ self._attach(obj)
+ self.uow.register_new(obj)
- def _update_impl(self, object, **kwargs):
- if self._is_attached(object) and object not in self.deleted:
+ def _update_impl(self, obj, **kwargs):
+ if self._is_attached(obj) and obj not in self.deleted:
return
- if not hasattr(object, '_instance_key'):
- raise exceptions.InvalidRequestError("Instance '%s' is not persisted" % repr(object))
- self._attach(object)
-
- def _register_pending(self, obj):
+ if not hasattr(obj, '_instance_key'):
+ raise exceptions.InvalidRequestError("Instance '%s' is not persisted" % repr(obj))
self._attach(obj)
- self.uow.register_new(obj)
def _register_persistent(self, obj):
- self._attach(obj)
- self.uow.register_clean(obj)
-
- def _register_deleted(self, obj):
- self._attach(obj)
- self.uow.register_deleted(obj)
+ obj._sa_session_id = self.hash_key
+ self.identity_map[obj._instance_key] = obj
+ attribute_manager.commit(obj)
def _attach(self, obj):
- """Attach the given object to this ``Session``."""
-
old_id = getattr(obj, '_sa_session_id', None)
if old_id != self.hash_key:
if old_id is not None and old_id in _sessions:
@@ -1024,9 +1016,6 @@ class Session(object):
return iter(list(self.uow.new) + self.uow.identity_map.values())
- def _get(self, key):
- return self.identity_map[key]
-
dirty = property(lambda s:s.uow.locate_dirty(),
doc="A ``Set`` of all objects marked as 'dirty' within this ``Session``")
@@ -1036,11 +1025,6 @@ class Session(object):
new = property(lambda s:s.uow.new,
doc="A ``Set`` of all objects marked as 'new' within this ``Session``.")
- def import_instance(self, *args, **kwargs):
- """A synynom for ``merge()``."""
-
- return self.merge(*args, **kwargs)
- import_instance = util.deprecated(import_instance)
# this is the AttributeManager instance used to provide attribute behavior on objects.
# to all the "global variable police" out there: its a stateless object.
diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py
index bdb17e1d6..86be77ae0 100644
--- a/lib/sqlalchemy/orm/strategies.py
+++ b/lib/sqlalchemy/orm/strategies.py
@@ -62,22 +62,22 @@ class ColumnLoader(LoaderStrategy):
if c not in row:
break
else:
- def execute(instance, row, isnew, ispostselect=None, **flags):
- if isnew or ispostselect:
- if self._should_log_debug:
- self.logger.debug("populating %s with %s/%s..." % (mapperutil.attribute_str(instance, self.key), row.__class__.__name__, self.columns[0].key))
- instance.__dict__[self.key] = self.parent_property.composite_class(*[row[c] for c in self.columns])
- self.logger.debug("Returning active composite column fetcher for %s %s" % (mapper, self.key))
- return (execute, None)
+ def new_execute(instance, row, **flags):
+ if self._should_log_debug:
+ self.logger.debug("populating %s with %s/%s..." % (mapperutil.attribute_str(instance, self.key), row.__class__.__name__, self.columns[0].key))
+ instance.__dict__[self.key] = self.parent_property.composite_class(*[row[c] for c in self.columns])
+ if self._should_log_debug:
+ self.logger.debug("Returning active composite column fetcher for %s %s" % (mapper, self.key))
+ return (new_execute, None, None)
elif self.columns[0] in row:
- def execute(instance, row, isnew, ispostselect=None, **flags):
- if isnew or ispostselect:
- if self._should_log_debug:
- self.logger.debug("populating %s with %s/%s" % (mapperutil.attribute_str(instance, self.key), row.__class__.__name__, self.columns[0].key))
- instance.__dict__[self.key] = row[self.columns[0]]
- self.logger.debug("Returning active column fetcher for %s %s" % (mapper, self.key))
- return (execute, None)
+ def new_execute(instance, row, **flags):
+ if self._should_log_debug:
+ self.logger.debug("populating %s with %s/%s" % (mapperutil.attribute_str(instance, self.key), row.__class__.__name__, self.columns[0].key))
+ instance.__dict__[self.key] = row[self.columns[0]]
+ if self._should_log_debug:
+ self.logger.debug("Returning active column fetcher for %s %s" % (mapper, self.key))
+ return (new_execute, None, None)
# our mapped column is not present in the row. check if we need to initialize a polymorphic
# row fetcher used by inheritance.
@@ -87,15 +87,17 @@ class ColumnLoader(LoaderStrategy):
if hosted_mapper.polymorphic_fetch == 'deferred':
# 'deferred' polymorphic row fetcher, put a callable on the property.
- def execute(instance, row, isnew, **flags):
+ def new_execute(instance, row, isnew, **flags):
if isnew:
sessionlib.attribute_manager.init_instance_attribute(instance, self.key, callable_=self._get_deferred_inheritance_loader(instance, mapper, needs_tables))
- self.logger.debug("Returning deferred column fetcher for %s %s" % (mapper, self.key))
- return (execute, None)
+ if self._should_log_debug:
+ self.logger.debug("Returning deferred column fetcher for %s %s" % (mapper, self.key))
+ return (new_execute, None, None)
else:
# immediate polymorphic row fetcher. no processing needed for this row.
- self.logger.debug("Returning no column fetcher for %s %s" % (mapper, self.key))
- return (None, None)
+ if self._should_log_debug:
+ self.logger.debug("Returning no column fetcher for %s %s" % (mapper, self.key))
+ return (None, None, None)
def _get_deferred_inheritance_loader(self, instance, mapper, needs_tables):
def create_statement():
@@ -125,19 +127,17 @@ class DeferredColumnLoader(LoaderStrategy):
if self.group is not None and selectcontext.attributes.get(('undefer', self.group), False):
return self.parent_property._get_strategy(ColumnLoader).create_row_processor(selectcontext, mapper, row)
elif not self.is_class_level or len(selectcontext.options):
- def execute(instance, row, isnew, **flags):
- if isnew:
- if self._should_log_debug:
- self.logger.debug("set deferred callable on %s" % mapperutil.attribute_str(instance, self.key))
- sessionlib.attribute_manager.init_instance_attribute(instance, self.key, callable_=self.setup_loader(instance))
- return (execute, None)
+ def new_execute(instance, row, **flags):
+ if self._should_log_debug:
+ self.logger.debug("set deferred callable on %s" % mapperutil.attribute_str(instance, self.key))
+ sessionlib.attribute_manager.init_instance_attribute(instance, self.key, callable_=self.setup_loader(instance))
+ return (new_execute, None, None)
else:
- def execute(instance, row, isnew, **flags):
- if isnew:
- if self._should_log_debug:
- self.logger.debug("set deferred callable on %s" % mapperutil.attribute_str(instance, self.key))
- sessionlib.attribute_manager.reset_instance_attribute(instance, self.key)
- return (execute, None)
+ def new_execute(instance, row, **flags):
+ if self._should_log_debug:
+ self.logger.debug("set deferred callable on %s" % mapperutil.attribute_str(instance, self.key))
+ sessionlib.attribute_manager.reset_instance_attribute(instance, self.key)
+ return (new_execute, None, None)
def init(self):
super(DeferredColumnLoader, self).init()
@@ -250,7 +250,7 @@ class DynaLoader(AbstractRelationLoader):
self._register_attribute(self.parent.class_, dynamic=True, target_mapper=self.parent_property.mapper)
def create_row_processor(self, selectcontext, mapper, row):
- return (None, None)
+ return (None, None, None)
DynaLoader.logger = logging.class_logger(DynaLoader)
@@ -260,12 +260,12 @@ class NoLoader(AbstractRelationLoader):
self._register_attribute(self.parent.class_)
def create_row_processor(self, selectcontext, mapper, row):
- def execute(instance, row, isnew, **flags):
- if isnew:
+ def new_execute(instance, row, ispostselect, **flags):
+ if not ispostselect:
if self._should_log_debug:
self.logger.debug("initializing blank scalar/collection on %s" % mapperutil.attribute_str(instance, self.key))
self._init_instance_attribute(instance)
- return (execute, None)
+ return (new_execute, None, None)
NoLoader.logger = logging.class_logger(NoLoader)
@@ -314,7 +314,8 @@ class LazyLoader(AbstractRelationLoader):
return prop._get_strategy(LazyLoader).setup_loader(instance)
def lazyload():
- self.logger.debug("lazy load attribute %s on instance %s" % (self.key, mapperutil.instance_str(instance)))
+ if self._should_log_debug:
+ self.logger.debug("lazy load attribute %s on instance %s" % (self.key, mapperutil.instance_str(instance)))
if not mapper.has_identity(instance):
return None
@@ -362,17 +363,17 @@ class LazyLoader(AbstractRelationLoader):
def create_row_processor(self, selectcontext, mapper, row):
if not self.is_class_level or len(selectcontext.options):
- def execute(instance, row, isnew, **flags):
- if isnew:
+ def new_execute(instance, row, ispostselect, **flags):
+ if not ispostselect:
if self._should_log_debug:
self.logger.debug("set instance-level lazy loader on %s" % mapperutil.attribute_str(instance, self.key))
# we are not the primary manager for this attribute on this class - set up a per-instance lazyloader,
# which will override the class-level behavior
self._init_instance_attribute(instance, callable_=self.setup_loader(instance, selectcontext.options))
- return (execute, None)
+ return (new_execute, None, None)
else:
- def execute(instance, row, isnew, **flags):
- if isnew:
+ def new_execute(instance, row, ispostselect, **flags):
+ if not ispostselect:
if self._should_log_debug:
self.logger.debug("set class-level lazy loader on %s" % mapperutil.attribute_str(instance, self.key))
# we are the primary manager for this attribute on this class - reset its per-instance attribute state,
@@ -380,7 +381,7 @@ class LazyLoader(AbstractRelationLoader):
# this usually is not needed unless the constructor of the object referenced the attribute before we got
# to load data into it.
sessionlib.attribute_manager.reset_instance_attribute(instance, self.key)
- return (execute, None)
+ return (new_execute, None, None)
def _create_lazy_clause(cls, prop, reverse_direction=False):
(primaryjoin, secondaryjoin, remote_side) = (prop.polymorphic_primaryjoin, prop.polymorphic_secondaryjoin, prop.remote_side)
@@ -591,7 +592,7 @@ class EagerLoader(AbstractRelationLoader):
# FIXME: instead of...
sessionlib.attribute_manager.get_attribute(instance, self.key).set_raw_value(instance, self.select_mapper._instance(selectcontext, decorated_row, None))
# bypass and set directly:
- #instance.__dict__[self.key] = ...
+ #instance.__dict__[self.key] = self.select_mapper._instance(selectcontext, decorated_row, None)
else:
# call _instance on the row, even though the object has been created,
# so that we further descend into properties
@@ -614,9 +615,10 @@ class EagerLoader(AbstractRelationLoader):
selectcontext.stack.pop()
selectcontext.stack.pop()
- return (execute, None)
+ return (execute, execute, None)
else:
- self.logger.debug("eager loader %s degrading to lazy loader" % str(self))
+ if self._should_log_debug:
+ self.logger.debug("eager loader %s degrading to lazy loader" % str(self))
selectcontext.stack.pop()
return self.parent_property._get_strategy(LazyLoader).create_row_processor(selectcontext, mapper, row)
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index 2f285cf46..74ece20c3 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -121,8 +121,9 @@ class UnitOfWork(object):
else:
return True
- def register_clean(self, obj):
- """register the given object as 'clean' (i.e. persistent) within this unit of work."""
+ def _register_clean(self, obj):
+ """register the given object as 'clean' (i.e. persistent) within this unit of work, after
+ a save operation has taken place."""
if obj in self.new:
self.new.remove(obj)
@@ -429,7 +430,7 @@ class UOWTransaction(object):
if elem.isdelete:
self.uow._remove_deleted(elem.obj)
else:
- self.uow.register_clean(elem.obj)
+ self.uow._register_clean(elem.obj)
def _sort_dependencies(self):
"""Create a hierarchical tree of dependent UOWTask instances.