diff options
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 13 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 39 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/session.py | 46 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/strategies.py | 92 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/unitofwork.py | 7 |
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. |
