summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/interfaces.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm/interfaces.py')
-rw-r--r--lib/sqlalchemy/orm/interfaces.py284
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()