### Extending Mapper {@name=extending}
-Mappers can have functionality augmented or replaced at many points in its execution via the usage of the MapperExtension class. This class is just a series of "hooks" where various functionality takes place. An application can make its own MapperExtension objects, overriding only the methods it needs. Methods that are not overridden return the special value `sqlalchemy.orm.mapper.EXT_PASS`, which indicates the operation should proceed as normally.
+Mappers can have functionality augmented or replaced at many points in its execution via the usage of the MapperExtension class. This class is just a series of "hooks" where various functionality takes place. An application can make its own MapperExtension objects, overriding only the methods it needs. Methods that are not overridden return the special value `sqlalchemy.orm.EXT_CONTINUE` to allow processing to continue to the next MapperExtension or simply proceed normally if there are no more extensions.
{python}
class MapperExtension(object):
- """base implementation for an object that provides overriding behavior to various
- Mapper functions. For each method in MapperExtension, a result of EXT_PASS indicates
- the functionality is not overridden."""
+ """Base implementation for customizing Mapper behavior.
+
+ For each method in MapperExtension, returning a result of
+ EXT_CONTINUE will allow processing to continue to the next
+ MapperExtension in line or use the default functionality if there
+ are no other extensions.
+
+ Returning EXT_STOP will halt processing of further extensions
+ handling that method. Some methods such as ``load`` have other
+ return requirements, see the individual documentation for details.
+ Other than these exception cases, any return value other than
+ EXT_CONTINUE or EXT_STOP will be interpreted as equivalent to
+ EXT_STOP.
+
+ EXT_PASS is a synonym for EXT_CONTINUE and is provided for
+ backward compatibility.
+ """
+
def get_session(self):
"""called to retrieve a contextual Session instance with which to
register a new object. Note: this is not called if a session is
provided with the __init__ params (i.e. _sa_session)"""
- return EXT_PASS
+ return EXT_CONTINUE
def select_by(self, query, *args, **kwargs):
"""overrides the select_by method of the Query object"""
- return EXT_PASS
+ return EXT_CONTINUE
def select(self, query, *args, **kwargs):
"""overrides the select method of the Query object"""
- return EXT_PASS
+ return EXT_CONTINUE
def create_instance(self, mapper, selectcontext, row, class_):
"""called when a new object instance is about to be created from a row.
the method can choose to create the instance itself, or it can return
class_ - the class we are mapping.
"""
- return EXT_PASS
+ return EXT_CONTINUE
def append_result(self, mapper, selectcontext, row, instance, identitykey, result, isnew):
"""called when an object instance is being appended to a result list.
- If this method returns EXT_PASS, it is assumed that the mapper should do the appending, else
+ If this method returns EXT_CONTINUE, it is assumed that the mapper should do the appending, else
if this method returns any other value or None, it is assumed that the append was handled by this method.
mapper - the mapper doing the operation
set. if you are selecting from a join, such as an eager load, you might see the same object instance
many times in the same result set.
"""
- return EXT_PASS
+ return EXT_CONTINUE
def populate_instance(self, mapper, selectcontext, row, instance, identitykey, isnew):
"""called right before the mapper, after creating an instance from a row, passes the row
to its MapperProperty objects which are responsible for populating the object's attributes.
- If this method returns EXT_PASS, it is assumed that the mapper should do the appending, else
+ If this method returns EXT_CONTINUE, it is assumed that the mapper should do the appending, else
if this method returns any other value or None, it is assumed that the append was handled by this method.
Essentially, this method is used to have a different mapper populate the object:
othermapper.populate_instance(selectcontext, instance, row, identitykey, isnew, frommapper=mapper)
return True
"""
- return EXT_PASS
+ return EXT_CONTINUE
def before_insert(self, mapper, connection, instance):
"""called before an object instance is INSERTed into its table.
this is a good place to set up primary key values and such that arent handled otherwise."""
- return EXT_PASS
+ return EXT_CONTINUE
def before_update(self, mapper, connection, instance):
"""called before an object instance is UPDATED"""
- return EXT_PASS
+ return EXT_CONTINUE
def after_update(self, mapper, connection, instance):
"""called after an object instance is UPDATED"""
- return EXT_PASS
+ return EXT_CONTINUE
def after_insert(self, mapper, connection, instance):
"""called after an object instance has been INSERTed"""
- return EXT_PASS
+ return EXT_CONTINUE
def before_delete(self, mapper, connection, instance):
"""called before an object instance is DELETEed"""
- return EXT_PASS
+ return EXT_CONTINUE
def after_delete(self, mapper, connection, instance):
"""called after an object instance is DELETEed"""
- return EXT_PASS
+ return EXT_CONTINUE
To use MapperExtension, make your own subclass of it and just send it off to a mapper:
{python}
entity_name = kwargs.pop('_sa_entity_name', None)
if session is None:
session = ext.get_session() # get Session from this Mapper's MapperExtension
- if session is EXT_PASS:
+ if session is EXT_CONTINUE:
session = None
if session is not None:
session.save(self, entity_name=entity_name) # attach to the current session
"""illustrates one way to use a custom pickler that is session-aware."""
from sqlalchemy import *
+from sqlalchemy.orm import *
from sqlalchemy.orm.session import object_session
from cStringIO import StringIO
from pickle import Pickler, Unpickler
class MyExt(MapperExtension):
def populate_instance(self, mapper, selectcontext, row, instance, identitykey, isnew):
MyPickler.sessions.current = selectcontext.session
- return EXT_PASS
+ return EXT_CONTINUE
def before_insert(self, mapper, connection, instance):
MyPickler.sessions.current = object_session(instance)
- return EXT_PASS
+ return EXT_CONTINUE
def before_update(self, mapper, connection, instance):
MyPickler.sessions.current = object_session(instance)
- return EXT_PASS
+ return EXT_CONTINUE
class MyPickler(object):
sessions = threading.local()
def select(self, query, arg=None, **kwargs):
if isinstance(arg, sql.FromClause) and arg.supports_execution():
- return orm.EXT_PASS
+ return orm.EXT_CONTINUE
else:
if arg is not None:
query = query.filter(arg)
from sqlalchemy.util import ScopedRegistry
-from sqlalchemy.orm import create_session, object_session, MapperExtension, EXT_PASS
+from sqlalchemy.orm import create_session, object_session, MapperExtension, EXT_CONTINUE
__all__ = ['SessionContext', 'SessionContextExt']
def init_instance(self, mapper, class_, instance, args, kwargs):
session = kwargs.pop('_sa_session', self.context.current)
session._save_impl(instance, entity_name=kwargs.pop('_sa_entity_name', None))
- return EXT_PASS
+ return EXT_CONTINUE
def init_failed(self, mapper, class_, instance, args, kwargs):
object_session(instance).expunge(instance)
- return EXT_PASS
+ return EXT_CONTINUE
def dispose_class(self, mapper, class_):
if hasattr(class_, '__init__') and hasattr(class_.__init__, '_oldinit'):
# the MIT License: http://www.opensource.org/licenses/mit-license.php
"""
-The mapper package provides object-relational functionality, building upon the schema and sql
-packages and tying operations to class properties and constructors.
+The mapper package provides object-relational functionality, building upon
+the schema and sql packages and tying operations to class properties and
+constructors.
"""
from sqlalchemy import exceptions
from sqlalchemy import util as sautil
from sqlalchemy.orm.mapper import Mapper, object_mapper, class_mapper, mapper_registry
-from sqlalchemy.orm.interfaces import SynonymProperty, MapperExtension, EXT_PASS, ExtensionOption, PropComparator
+from sqlalchemy.orm.interfaces import SynonymProperty, MapperExtension, EXT_CONTINUE, EXT_STOP, EXT_PASS, ExtensionOption, PropComparator
from sqlalchemy.orm.properties import PropertyLoader, ColumnProperty, CompositeProperty, BackRef
from sqlalchemy.orm import mapper as mapperlib
from sqlalchemy.orm import collections, strategies
from sqlalchemy.orm.session import Session as _Session
from sqlalchemy.orm.session import object_session, attribute_manager
-__all__ = ['relation', 'column_property', 'composite', 'backref', 'eagerload',
- 'eagerload_all', 'lazyload', 'noload', 'deferred', 'defer', 'undefer',
- 'undefer_group', 'extension', 'mapper', 'clear_mappers',
- 'compile_mappers', 'class_mapper', 'object_mapper', 'dynamic_loader',
- 'MapperExtension', 'Query', 'polymorphic_union', 'create_session','Session',
- 'synonym', 'contains_alias', 'contains_eager', 'EXT_PASS',
- 'object_session', 'PropComparator'
- ]
+__all__ = [ 'relation', 'column_property', 'composite', 'backref', 'eagerload',
+ 'eagerload_all', 'lazyload', 'noload', 'deferred', 'defer',
+ 'undefer', 'undefer_group', 'extension', 'mapper', 'clear_mappers',
+ 'compile_mappers', 'class_mapper', 'object_mapper',
+ 'dynamic_loader', 'MapperExtension', 'Query', 'polymorphic_union',
+ 'create_session', 'Session', 'synonym', 'contains_alias',
+ 'contains_eager', 'EXT_CONTINUE', 'EXT_STOP', 'EXT_PASS',
+ 'object_session', 'PropComparator' ]
+
def create_session(bind=None, **kwargs):
"""create a new version 0.3-style [sqlalchemy.orm.session#Session].
from sqlalchemy import util, logging, sql
-# returned by a MapperExtension method to indicate a "do nothing" response
-EXT_PASS = object()
+__all__ = ['EXT_CONTINUE', 'EXT_STOP', 'EXT_PASS', 'MapperExtension',
+ 'MapperProperty', 'PropComparator', 'StrategizedProperty',
+ 'LoaderStack', 'OperationContext', 'MapperOption',
+ 'ExtensionOption', 'SynonymProperty', 'PropertyOption',
+ 'AttributeExtension', 'StrategizedOption', 'LoaderStrategy' ]
+
+EXT_CONTINUE = EXT_PASS = object()
+EXT_STOP = object()
class MapperExtension(object):
- """Base implementation for an object that provides overriding
- behavior to various Mapper functions. For each method in
- MapperExtension, a result of EXT_PASS indicates the functionality
- is not overridden.
+ """Base implementation for customizing Mapper behavior.
+
+ For each method in MapperExtension, returning a result of
+ EXT_CONTINUE will allow processing to continue to the next
+ MapperExtension in line or use the default functionality if there
+ are no other extensions.
+
+ Returning EXT_STOP will halt processing of further extensions
+ handling that method. Some methods such as ``load`` have other
+ return requirements, see the individual documentation for details.
+ Other than these exception cases, any return value other than
+ EXT_CONTINUE or EXT_STOP will be interpreted as equivalent to
+ EXT_STOP.
+
+ EXT_PASS is a synonym for EXT_CONTINUE and is provided for
+ backward compatibility.
"""
-
def init_instance(self, mapper, class_, instance, args, kwargs):
- return EXT_PASS
+ return EXT_CONTINUE
def init_failed(self, mapper, class_, instance, args, kwargs):
- return EXT_PASS
+ return EXT_CONTINUE
def get_session(self):
"""Retrieve a contextual Session instance with which to
`__init__` params (i.e. `_sa_session`).
"""
- return EXT_PASS
+ return EXT_CONTINUE
def load(self, query, *args, **kwargs):
"""Override the `load` method of the Query object.
The return value of this method is used as the result of
- ``query.load()`` if the value is anything other than EXT_PASS.
+ ``query.load()`` if the value is anything other than EXT_CONTINUE.
"""
- return EXT_PASS
+ return EXT_CONTINUE
def get(self, query, *args, **kwargs):
"""Override the `get` method of the Query object.
The return value of this method is used as the result of
- ``query.get()`` if the value is anything other than EXT_PASS.
+ ``query.get()`` if the value is anything other than EXT_CONTINUE.
"""
- return EXT_PASS
+ return EXT_CONTINUE
def get_by(self, query, *args, **kwargs):
"""Override the `get_by` method of the Query object.
The return value of this method is used as the result of
``query.get_by()`` if the value is anything other than
- EXT_PASS.
+ EXT_CONTINUE.
DEPRECATED.
"""
- return EXT_PASS
+ return EXT_CONTINUE
def select_by(self, query, *args, **kwargs):
"""Override the `select_by` method of the Query object.
The return value of this method is used as the result of
``query.select_by()`` if the value is anything other than
- EXT_PASS.
+ EXT_CONTINUE.
DEPRECATED.
"""
- return EXT_PASS
+ return EXT_CONTINUE
def select(self, query, *args, **kwargs):
"""Override the `select` method of the Query object.
The return value of this method is used as the result of
``query.select()`` if the value is anything other than
- EXT_PASS.
+ EXT_CONTINUE.
DEPRECATED.
"""
- return EXT_PASS
+ return EXT_CONTINUE
def translate_row(self, mapper, context, row):
method.
"""
- return EXT_PASS
+ return EXT_CONTINUE
def create_instance(self, mapper, selectcontext, row, class_):
"""Receive a row when a new object instance is about to be
The class we are mapping.
"""
- return EXT_PASS
+ return EXT_CONTINUE
def append_result(self, mapper, selectcontext, row, instance, result, **flags):
"""Receive an object instance before that instance is appended
to a result list.
- If this method returns EXT_PASS, result appending will proceed
+ If this method returns EXT_CONTINUE, result appending will proceed
normally. if this method returns any other value or None,
result appending will not proceed for this instance, giving
this extension an opportunity to do the appending itself, if
`create_row_processor()` method of [sqlalchemy.orm.interfaces#MapperProperty]
"""
- return EXT_PASS
+ return EXT_CONTINUE
def populate_instance(self, mapper, selectcontext, row, instance, **flags):
"""Receive a newly-created instance before that instance has
The normal population of attributes is according to each
attribute's corresponding MapperProperty (which includes
column-based attributes as well as relationships to other
- classes). If this method returns EXT_PASS, instance
+ classes). If this method returns EXT_CONTINUE, instance
population will proceed normally. If any other value or None
is returned, instance population will not proceed, giving this
extension an opportunity to populate the instance itself, if
desired.
"""
- return EXT_PASS
+ return EXT_CONTINUE
def before_insert(self, mapper, connection, instance):
"""Receive an object instance before that instance is INSERTed
that aren't handled otherwise.
"""
- return EXT_PASS
+ return EXT_CONTINUE
+
+ def after_insert(self, mapper, connection, instance):
+ """Receive an object instance after that instance is INSERTed."""
+
+ return EXT_CONTINUE
def before_update(self, mapper, connection, instance):
"""Receive an object instance before that instance is UPDATEed."""
- return EXT_PASS
+ return EXT_CONTINUE
def after_update(self, mapper, connection, instance):
"""Receive an object instance after that instance is UPDATEed."""
- return EXT_PASS
-
- def after_insert(self, mapper, connection, instance):
- """Receive an object instance after that instance is INSERTed."""
-
- return EXT_PASS
+ return EXT_CONTINUE
def before_delete(self, mapper, connection, instance):
"""Receive an object instance before that instance is DELETEed."""
- return EXT_PASS
+ return EXT_CONTINUE
def after_delete(self, mapper, connection, instance):
"""Receive an object instance after that instance is DELETEed."""
- return EXT_PASS
+ return EXT_CONTINUE
class MapperProperty(object):
"""Manage the relationship of a ``Mapper`` to a single class
from sqlalchemy.orm import util as mapperutil
from sqlalchemy.orm.util import ExtensionCarrier
from sqlalchemy.orm import sync
-from sqlalchemy.orm.interfaces import MapperProperty, EXT_PASS, MapperExtension, SynonymProperty
+from sqlalchemy.orm.interfaces import MapperProperty, EXT_CONTINUE, MapperExtension, SynonymProperty
import weakref, warnings, operator
__all__ = ['Mapper', 'class_mapper', 'object_mapper', 'mapper_registry']
self.compile()
s = self.extension.get_session()
- if s is EXT_PASS:
+ if s is EXT_CONTINUE:
raise exceptions.InvalidRequestError("No contextual Session is established. Use a MapperExtension that implements get_session or use 'import sqlalchemy.mods.threadlocal' to establish a default thread-local contextual session.")
return s
extension = self.extension
ret = extension.translate_row(self, context, row)
- if ret is not EXT_PASS:
+ if ret is not EXT_CONTINUE:
row = ret
if not skip_polymorphic and self.polymorphic_on is not None:
if not context.identity_map.has_key(identitykey):
context.identity_map[identitykey] = instance
isnew = True
- if extension.populate_instance(self, context, row, instance, **{'instancekey':identitykey, 'isnew':isnew}) is EXT_PASS:
+ if extension.populate_instance(self, context, row, instance, **{'instancekey':identitykey, 'isnew':isnew}) is EXT_CONTINUE:
self.populate_instance(context, instance, row, **{'instancekey':identitykey, 'isnew':isnew})
- if extension.append_result(self, context, row, instance, result, **{'instancekey':identitykey, 'isnew':isnew}) is EXT_PASS:
+ if extension.append_result(self, context, row, instance, result, **{'instancekey':identitykey, 'isnew':isnew}) is EXT_CONTINUE:
if result is not None:
result.append(instance)
return instance
# plugin point
instance = extension.create_instance(self, context, row, self.class_)
- if instance is EXT_PASS:
+ if instance is EXT_CONTINUE:
instance = self._create_instance(context.session)
else:
instance._entity_name = self.entity_name
# call further mapper properties on the row, to pull further
# instances from the row and possibly populate this item.
flags = {'instancekey':identitykey, 'isnew':isnew}
- if extension.populate_instance(self, context, row, instance, **flags) is EXT_PASS:
+ if extension.populate_instance(self, context, row, instance, **flags) is EXT_CONTINUE:
self.populate_instance(context, instance, row, **flags)
- if extension.append_result(self, context, row, instance, result, **flags) is EXT_PASS:
+ if extension.append_result(self, context, row, instance, result, **flags) is EXT_CONTINUE:
if result is not None:
result.append(instance)
return instance
"""
ret = self._extension.get(self, ident, **kwargs)
- if ret is not mapper.EXT_PASS:
+ if ret is not mapper.EXT_CONTINUE:
return ret
# convert composite types to individual args
"""
ret = self._extension.load(self, ident, **kwargs)
- if ret is not mapper.EXT_PASS:
+ if ret is not mapper.EXT_CONTINUE:
return ret
key = self.mapper.identity_key_from_primary_key(ident)
instance = self._get(key, ident, reload=True, **kwargs)
"""DEPRECATED. use query.filter_by(\**params).first()"""
ret = self._extension.get_by(self, *args, **params)
- if ret is not mapper.EXT_PASS:
+ if ret is not mapper.EXT_CONTINUE:
return ret
return self._legacy_filter_by(*args, **params).first()
"""DEPRECATED. use use query.filter_by(\**params).all()."""
ret = self._extension.select_by(self, *args, **params)
- if ret is not mapper.EXT_PASS:
+ if ret is not mapper.EXT_CONTINUE:
return ret
return self._legacy_filter_by(*args, **params).list()
"""DEPRECATED. use query.filter(whereclause).all(), or query.from_statement(statement).all()"""
ret = self._extension.select(self, arg=arg, **kwargs)
- if ret is not mapper.EXT_PASS:
+ if ret is not mapper.EXT_CONTINUE:
return ret
return self._build_select(arg, **kwargs).all()
# the MIT License: http://www.opensource.org/licenses/mit-license.php
from sqlalchemy import sql, util, exceptions, sql_util
-from sqlalchemy.orm.interfaces import MapperExtension, EXT_PASS
+from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE
all_cascades = util.Set(["delete", "delete-orphan", "all", "merge",
"expunge", "save-update", "refresh-expire", "none"])
def _do(self, *args, **kwargs):
for elem in self.__elements:
ret = getattr(elem, funcname)(*args, **kwargs)
- if ret is not EXT_PASS:
+ if ret is not EXT_CONTINUE:
return ret
else:
- return EXT_PASS
+ return EXT_CONTINUE
return _do
init_instance = _create_do('init_instance')
class ext1(MapperExtension):
def populate_instance(self, mapper, selectcontext, row, instance, **flags):
instance.TEST = "hello world"
- return EXT_PASS
+ return EXT_CONTINUE
assert query.options(extension(ext1()))[0].TEST == "hello world"
def test_order_by(self):
def populate_instance(self, mapper, selectcontext, row, instance, **flags):
"""test options at the Mapper._instance level"""
instance.TEST = "hello world"
- return EXT_PASS
+ return EXT_CONTINUE
mapper(User, users, extension=ext1(), properties={
'addresses':relation(mapper(Address, addresses), lazy=False)
})
def populate_instance(self, mapper, selectcontext, row, instance, **flags):
"""test options at the Mapper._instance level"""
instance.TEST_2 = "also hello world"
- return EXT_PASS
+ return EXT_CONTINUE
l = sess.query(User).options(extension(testext())).select_by(x=5)
assert l == "HI"
l = sess.query(User).options(extension(testext())).get(7)