diff options
Diffstat (limited to 'lib/sqlalchemy/orm/interfaces.py')
-rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 284 |
1 files changed, 146 insertions, 138 deletions
diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 0b26086cd..536d94658 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -1,5 +1,6 @@ # interfaces.py -# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer mike_mp@zzzcomputing.com +# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Michael Bayer +# mike_mp@zzzcomputing.com # # This module is part of SQLAlchemy and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php @@ -155,7 +156,8 @@ class MapperExtension(object): """ return EXT_CONTINUE - def append_result(self, mapper, selectcontext, row, instance, result, **flags): + def append_result(self, mapper, selectcontext, row, instance, + result, **flags): """Receive an object instance before that instance is appended to a result list. @@ -182,12 +184,14 @@ class MapperExtension(object): \**flags extra information about the row, same as criterion in - ``create_row_processor()`` method of :class:`~sqlalchemy.orm.interfaces.MapperProperty` + ``create_row_processor()`` method of + :class:`~sqlalchemy.orm.interfaces.MapperProperty` """ return EXT_CONTINUE - def populate_instance(self, mapper, selectcontext, row, instance, **flags): + def populate_instance(self, mapper, selectcontext, row, + instance, **flags): """Receive an instance before that instance has its attributes populated. @@ -266,17 +270,18 @@ class MapperExtension(object): Note that this method is called for all instances that are marked as "dirty", even those which have no net changes to their column-based - attributes. An object is marked as dirty when any of its column-based + attributes. An object is marked as dirty when any of its column-based attributes have a "set attribute" operation called or when any of its - collections are modified. If, at update time, no column-based attributes - have any net changes, no UPDATE statement will be issued. This means - that an instance being sent to before_update is *not* a guarantee that - an UPDATE statement will be issued (although you can affect the outcome - here). - - To detect if the column-based attributes on the object have net changes, - and will therefore generate an UPDATE statement, use - ``object_session(instance).is_modified(instance, include_collections=False)``. + collections are modified. If, at update time, no column-based + attributes have any net changes, no UPDATE statement will be issued. + This means that an instance being sent to before_update is *not* a + guarantee that an UPDATE statement will be issued (although you can + affect the outcome here). + + To detect if the column-based attributes on the object have net + changes, and will therefore generate an UPDATE statement, use + ``object_session(instance).is_modified(instance, + include_collections=False)``. Column-based attributes can be modified within this method which will result in the new value being updated. However @@ -328,77 +333,80 @@ class MapperExtension(object): return EXT_CONTINUE class SessionExtension(object): - """An extension hook object for Sessions. Subclasses may be installed into a Session - (or sessionmaker) using the ``extension`` keyword argument. - """ + + """An extension hook object for Sessions. Subclasses may be + installed into a Session (or sessionmaker) using the ``extension`` + keyword argument. """ def before_commit(self, session): """Execute right before commit is called. - - Note that this may not be per-flush if a longer running transaction is ongoing.""" + + Note that this may not be per-flush if a longer running + transaction is ongoing.""" def after_commit(self, session): """Execute after a commit has occured. - - Note that this may not be per-flush if a longer running transaction is ongoing.""" + + Note that this may not be per-flush if a longer running + transaction is ongoing.""" def after_rollback(self, session): """Execute after a rollback has occured. + + Note that this may not be per-flush if a longer running + transaction is ongoing.""" - Note that this may not be per-flush if a longer running transaction is ongoing.""" - - def before_flush(self, session, flush_context, instances): + def before_flush( self, session, flush_context, instances): """Execute before flush process has started. - - `instances` is an optional list of objects which were passed to the ``flush()`` - method. - """ + + `instances` is an optional list of objects which were passed to + the ``flush()`` method. """ def after_flush(self, session, flush_context): - """Execute after flush has completed, but before commit has been called. - - Note that the session's state is still in pre-flush, i.e. 'new', 'dirty', - and 'deleted' lists still show pre-flush state as well as the history - settings on instance attributes.""" + """Execute after flush has completed, but before commit has been + called. + + Note that the session's state is still in pre-flush, i.e. 'new', + 'dirty', and 'deleted' lists still show pre-flush state as well + as the history settings on instance attributes.""" def after_flush_postexec(self, session, flush_context): - """Execute after flush has completed, and after the post-exec state occurs. - - This will be when the 'new', 'dirty', and 'deleted' lists are in their final - state. An actual commit() may or may not have occured, depending on whether or not - the flush started its own transaction or participated in a larger transaction. - """ + """Execute after flush has completed, and after the post-exec + state occurs. + + This will be when the 'new', 'dirty', and 'deleted' lists are in + their final state. An actual commit() may or may not have + occured, depending on whether or not the flush started its own + transaction or participated in a larger transaction. """ - def after_begin(self, session, transaction, connection): + def after_begin( self, session, transaction, connection): """Execute after a transaction is begun on a connection - - `transaction` is the SessionTransaction. This method is called after an - engine level transaction is begun on a connection. - """ + + `transaction` is the SessionTransaction. This method is called + after an engine level transaction is begun on a connection. """ def after_attach(self, session, instance): """Execute after an instance is attached to a session. + + This is called after an add, delete or merge. """ - This is called after an add, delete or merge. - """ - - def after_bulk_update(self, session, query, query_context, result): + def after_bulk_update( self, session, query, query_context, result): """Execute after a bulk update operation to the session. - + This is called after a session.query(...).update() - - `query` is the query object that this update operation was called on. - `query_context` was the query context object. + + `query` is the query object that this update operation was + called on. `query_context` was the query context object. `result` is the result object returned from the bulk operation. """ - def after_bulk_delete(self, session, query, query_context, result): + def after_bulk_delete( self, session, query, query_context, result): """Execute after a bulk delete operation to the session. - + This is called after a session.query(...).delete() - - `query` is the query object that this delete operation was called on. - `query_context` was the query context object. + + `query` is the query object that this delete operation was + called on. `query_context` was the query context object. `result` is the result object returned from the bulk operation. """ @@ -437,13 +445,15 @@ class MapperProperty(object): Callables are of the following form:: def new_execute(state, dict_, row, isnew): - # process incoming instance state and given row. the instance is + # process incoming instance state and given row. + # the instance is # "new" and was just created upon receipt of this row. "isnew" indicates if the instance was newly created as a result of reading this row def existing_execute(state, dict_, row): - # process incoming instance state and given row. the instance is + # process incoming instance state and given row. the + # instance is # "existing" and was created based on a previous row. return (new_execute, existing_execute) @@ -454,7 +464,8 @@ class MapperProperty(object): raise NotImplementedError() - def cascade_iterator(self, type_, state, visited_instances=None, halt_on=None): + def cascade_iterator(self, type_, state, visited_instances=None, + halt_on=None): """Iterate through instances related to the given instance for a particular 'cascade', starting with this MapperProperty. @@ -484,17 +495,20 @@ class MapperProperty(object): @property def class_attribute(self): - """Return the class-bound descriptor corresponding to this MapperProperty.""" - - return getattr(self.parent.class_, self.key) - - def do_init(self): - """Perform subclass-specific initialization post-mapper-creation steps. + """Return the class-bound descriptor corresponding to this + MapperProperty.""" - This is a *template* method called by the - ``MapperProperty`` object's init() method. + return getattr(self.parent.class_, self.key) + def do_init(self): + """Perform subclass-specific initialization post-mapper-creation + steps. + + This is a template method called by the ``MapperProperty`` + object's init() method. + """ + pass def post_instrument_class(self, mapper): @@ -553,10 +567,11 @@ class PropComparator(expression.ColumnOperators): raise NotImplementedError("%r" % self) def adapted(self, adapter): - """Return a copy of this PropComparator which will use the given adaption function - on the local side of generated expressions. - + """Return a copy of this PropComparator which will use the given + adaption function on the local side of generated expressions. + """ + return self.__class__(self.prop, self.mapper, adapter) @staticmethod @@ -574,7 +589,8 @@ class PropComparator(expression.ColumnOperators): def of_type(self, class_): """Redefine this object in terms of a polymorphic subclass. - Returns a new PropComparator from which further criterion can be evaluated. + Returns a new PropComparator from which further criterion can be + evaluated. e.g.:: @@ -591,7 +607,8 @@ class PropComparator(expression.ColumnOperators): return self.operate(PropComparator.of_type_op, class_) def any(self, criterion=None, **kwargs): - """Return true if this collection contains any member that meets the given criterion. + """Return true if this collection contains any member that meets the + given criterion. criterion an optional ClauseElement formulated against the member class' table @@ -605,7 +622,8 @@ class PropComparator(expression.ColumnOperators): return self.operate(PropComparator.any_op, criterion, **kwargs) def has(self, criterion=None, **kwargs): - """Return true if this element references a member which meets the given criterion. + """Return true if this element references a member which meets the + given criterion. criterion an optional ClauseElement formulated against the member class' table @@ -630,7 +648,8 @@ class StrategizedProperty(MapperProperty): """ def _get_context_strategy(self, context, path): - cls = context.attributes.get(("loaderstrategy", _reduce_path(path)), None) + cls = context.attributes.get(('loaderstrategy', + _reduce_path(path)), None) if cls: try: return self.__all_strategies[cls] @@ -706,16 +725,19 @@ class MapperOption(object): pass def process_query_conditionally(self, query): - """same as process_query(), except that this option may not apply - to the given query. - + """same as process_query(), except that this option may not + apply to the given query. + Used when secondary loaders resend existing options to a new Query.""" + self.process_query(query) class ExtensionOption(MapperOption): - """a MapperOption that applies a MapperExtension to a query operation.""" - + + """a MapperOption that applies a MapperExtension to a query + operation.""" + def __init__(self, ext): self.ext = ext @@ -726,8 +748,7 @@ class ExtensionOption(MapperOption): class PropertyOption(MapperOption): """A MapperOption that is applied to a property off the mapper or - one of its child mappers, identified by a dot-separated key. - """ + one of its child mappers, identified by a dot-separated key. """ def __init__(self, key, mapper=None): self.key = key @@ -768,28 +789,24 @@ class PropertyOption(MapperOption): state['key'] = tuple(ret) self.__dict__ = state - def _find_entity(self, query, mapper, raiseerr): - from sqlalchemy.orm.util import _class_to_mapper, _is_aliased_class - + def _find_entity( self, query, mapper, raiseerr): + from sqlalchemy.orm.util import _class_to_mapper, \ + _is_aliased_class if _is_aliased_class(mapper): searchfor = mapper isa = False else: searchfor = _class_to_mapper(mapper) isa = True - for ent in query._mapper_entities: - if searchfor is ent.path_entity or ( - isa and - searchfor.common_parent(ent.path_entity)): + if searchfor is ent.path_entity or isa \ + and searchfor.common_parent(ent.path_entity): return ent else: if raiseerr: - raise sa_exc.ArgumentError( - "Can't find entity %s in Query. Current list: %r" - % (searchfor, [ - str(m.path_entity) for m in query._entities - ])) + raise sa_exc.ArgumentError("Can't find entity %s in " + "Query. Current list: %r" % (searchfor, + [str(m.path_entity) for m in query._entities])) else: return None @@ -798,18 +815,17 @@ class PropertyOption(MapperOption): entity = None l = [] mappers = [] - - # _current_path implies we're in a secondary load - # with an existing path + + # _current_path implies we're in a secondary load with an + # existing path + current_path = list(query._current_path) - tokens = [] for key in util.to_list(self.key): if isinstance(key, basestring): tokens += key.split('.') else: tokens += [key] - for token in tokens: if isinstance(token, basestring): if not entity: @@ -817,28 +833,23 @@ class PropertyOption(MapperOption): if current_path[1] == token: current_path = current_path[2:] continue - entity = query._entity_zero() path_element = entity.path_entity mapper = entity.mapper mappers.append(mapper) - prop = mapper.get_property( - token, - resolve_synonyms=True, - raiseerr=raiseerr) + prop = mapper.get_property(token, + resolve_synonyms=True, raiseerr=raiseerr) key = token elif isinstance(token, PropComparator): prop = token.property if not entity: if current_path: - if current_path[0:2] == [token.parententity, prop.key]: + if current_path[0:2] == [token.parententity, + prop.key]: current_path = current_path[2:] continue - - entity = self._find_entity( - query, - token.parententity, - raiseerr) + entity = self._find_entity(query, + token.parententity, raiseerr) if not entity: return [], [] path_element = entity.path_entity @@ -846,36 +857,29 @@ class PropertyOption(MapperOption): mappers.append(prop.parent) key = prop.key else: - raise sa_exc.ArgumentError("mapper option expects string key " - "or list of attributes") - + raise sa_exc.ArgumentError('mapper option expects ' + 'string key or list of attributes') if prop is None: return [], [] - path = build_path(path_element, prop.key, path) l.append(path) if getattr(token, '_of_type', None): path_element = mapper = token._of_type else: path_element = mapper = getattr(prop, 'mapper', None) - if path_element: path_element = path_element - - - # if current_path tokens remain, then - # we didn't have an exact path match. + if current_path: return [], [] - return l, mappers class AttributeExtension(object): """An event handler for individual attribute change events. - + AttributeExtension is assembled within the descriptors associated with a mapped class. - + """ active_history = True @@ -918,18 +922,22 @@ class StrategizedOption(PropertyOption): is_chained = False def process_query_property(self, query, paths, mappers): - # _get_context_strategy may receive the path in terms of - # a base mapper - e.g. options(eagerload_all(Company.employees, Engineer.machines)) - # in the polymorphic tests leads to "(Person, 'machines')" in - # the path due to the mechanics of how the eager strategy builds - # up the path + + # _get_context_strategy may receive the path in terms of a base + # mapper - e.g. options(eagerload_all(Company.employees, + # Engineer.machines)) in the polymorphic tests leads to + # "(Person, 'machines')" in the path due to the mechanics of how + # the eager strategy builds up the path + if self.is_chained: for path in paths: - query._attributes[("loaderstrategy", _reduce_path(path))] = \ - self.get_strategy_class() + query._attributes[('loaderstrategy', + _reduce_path(path))] = \ + self.get_strategy_class() else: - query._attributes[("loaderstrategy", _reduce_path(paths[-1]))] = \ - self.get_strategy_class() + query._attributes[('loaderstrategy', + _reduce_path(paths[-1]))] = \ + self.get_strategy_class() def get_strategy_class(self): raise NotImplementedError() @@ -987,13 +995,13 @@ class LoaderStrategy(object): def setup_query(self, context, entity, path, adapter, **kwargs): pass - def create_row_processor(self, selectcontext, path, mapper, row, adapter): - """Return row processing functions which fulfill the contract specified - by MapperProperty.create_row_processor. - - StrategizedProperty delegates its create_row_processor method directly - to this method. - """ + def create_row_processor(self, selectcontext, path, mapper, + row, adapter): + """Return row processing functions which fulfill the contract + specified by MapperProperty.create_row_processor. + + StrategizedProperty delegates its create_row_processor method + directly to this method. """ raise NotImplementedError() |