From c9dc3a334eee9bb9e281f5a64251979315079aab Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 21 Oct 2006 23:04:36 +0000 Subject: [PATCH] re-implemented extension option --- lib/sqlalchemy/orm/__init__.py | 9 ++++++++- lib/sqlalchemy/orm/interfaces.py | 9 +++++++-- lib/sqlalchemy/orm/mapper.py | 15 +++++++++++++-- lib/sqlalchemy/orm/query.py | 13 +++++++++++-- lib/sqlalchemy/orm/strategies.py | 2 ++ test/orm/mapper.py | 9 +++++++++ 6 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index c18da2005b..b5c590b741 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -17,7 +17,7 @@ from util import polymorphic_union import properties, strategies from session import Session as create_session -__all__ = ['relation', 'backref', 'eagerload', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', +__all__ = ['relation', 'backref', 'eagerload', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', 'extension', 'mapper', 'clear_mappers', 'clear_mapper', 'sql', 'class_mapper', 'object_mapper', 'MapperExtension', 'Query', 'cascade_mappers', 'polymorphic_union', 'create_session', 'synonym', 'contains_eager', 'EXT_PASS' ] @@ -73,6 +73,13 @@ def clear_mapper(m): new primary mapper.""" del mapper_registry[m.class_key] +def extension(ext): + """return a MapperOption that will insert the given MapperExtension to the + beginning of the list of extensions that will be called in the context of the Query. + + used with query.options().""" + return mapperlib.ExtensionOption(ext) + def eagerload(name): """return a MapperOption that will convert the property of the given name into an eager load. diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 8e505bac59..d6c4204f95 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -5,7 +5,7 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php -from sqlalchemy import util +from sqlalchemy import util, logging class MapperProperty(object): """manages the relationship of a Mapper to a single class attribute, as well @@ -102,7 +102,9 @@ class MapperOption(object): pass def process_selection_context(self, context): pass - + def process_query(self, query): + pass + 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.""" @@ -126,13 +128,16 @@ class PropertyOption(MapperOption): mapper = getattr(prop, 'mapper', None) self.__prop = prop return prop +PropertyOption.logger = logging.class_logger(PropertyOption) class StrategizedOption(PropertyOption): """a MapperOption that affects which LoaderStrategy will be used for an operation by a StrategizedProperty.""" def process_query_property(self, context, property): + self.logger.debug("applying option to QueryContext, property key '%s'" % self.key) context.attributes[(LoaderStrategy, property)] = self.get_strategy_class() def process_selection_property(self, context, property): + self.logger.debug("applying option to SelectionContext, property key '%s'" % self.key) context.attributes[(LoaderStrategy, property)] = self.get_strategy_class() def get_strategy_class(self): raise NotImplementedError() diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 575609d3cd..b3dbe7a921 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -1305,6 +1305,12 @@ class MapperExtension(object): class _ExtensionCarrier(MapperExtension): def __init__(self): self.elements = [] + def insert(self, extension): + """insert a MapperExtension at the beginning of this ExtensionCarrier's list.""" + self.elements.insert(0, extension) + def append(self, extension): + """append a MapperExtension at the end of this ExtensionCarrier's list.""" + self.elements.append(extension) # TODO: shrink down this approach using __getattribute__ or similar def get_session(self): return self._do('get_session') @@ -1334,14 +1340,19 @@ class _ExtensionCarrier(MapperExtension): def _do(self, funcname, *args, **kwargs): for elem in self.elements: if elem is self: - raise "WTF" + raise exceptions.AssertionError("ExtensionCarrier set to itself") ret = getattr(elem, funcname)(*args, **kwargs) if ret is not EXT_PASS: return ret else: return EXT_PASS - +class ExtensionOption(MapperExtension): + def __init__(self, ext): + self.ext = ext + def process_query(self, query): + query._insert_extension(self.ext) + class ClassKey(object): """keys a class and an entity name to a mapper, via the mapper_registry.""" __metaclass__ = util.ArgSingleton diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index ea70de90af..82ed4e1a02 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -14,7 +14,7 @@ __all__ = ['Query', 'QueryContext', 'SelectionContext'] class Query(object): """encapsulates the object-fetching operations provided by Mappers.""" - def __init__(self, class_or_mapper, session=None, entity_name=None, lockmode=None, with_options=None, **kwargs): + def __init__(self, class_or_mapper, session=None, entity_name=None, lockmode=None, with_options=None, extension=None, **kwargs): if isinstance(class_or_mapper, type): self.mapper = mapper.class_mapper(class_or_mapper, entity_name=entity_name) else: @@ -24,7 +24,10 @@ class Query(object): self.always_refresh = kwargs.pop('always_refresh', self.mapper.always_refresh) self.order_by = kwargs.pop('order_by', self.mapper.order_by) self.lockmode = lockmode - self.extension = kwargs.pop('extension', self.mapper.extension) + self.extension = mapper._ExtensionCarrier() + if extension is not None: + self.extension.append(extension) + self.extension.append(self.mapper.extension) self._session = session if not hasattr(self.mapper, '_get_clause'): _get_clause = sql.and_() @@ -32,6 +35,12 @@ class Query(object): _get_clause.clauses.append(primary_key == sql.bindparam(primary_key._label, type=primary_key.type)) self.mapper._get_clause = _get_clause self._get_clause = self.mapper._get_clause + for opt in self.with_options: + opt.process_query(self) + + def _insert_extension(self, ext): + self.extension.insert(ext) + def _get_session(self): if self._session is None: return self.mapper.get_session() diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 84512c5a93..916a60d252 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -524,6 +524,7 @@ class EagerLazyOption(StrategizedOption): return EagerLoader elif self.lazy is None: return NoLoader +EagerLazyOption.logger = logging.class_logger(EagerLazyOption) class RowDecorateOption(PropertyOption): def __init__(self, key, decorator=None): @@ -531,5 +532,6 @@ class RowDecorateOption(PropertyOption): self.decorator = decorator def process_selection_property(self, context, property): context.attributes[(EagerLoader, property)] = self.decorator +RowDecorateOption.logger = logging.class_logger(RowDecorateOption) diff --git a/test/orm/mapper.py b/test/orm/mapper.py index 4a401fde83..6c54a5bfbe 100644 --- a/test/orm/mapper.py +++ b/test/orm/mapper.py @@ -360,6 +360,15 @@ class MapperTest(MapperSuperTest): u = sess.query(User).get_by(uname='jack') self.assert_result(u.adlist, Address, *(user_address_result[0]['addresses'][1])) + + def testextensionoptions(self): + sess = create_session() + mapper(User, users) + class testext(MapperExtension): + def select_by(self, *args, **kwargs): + return "HI" + l = sess.query(User).options(extension(testext())).select_by(x=5) + assert l == "HI" def testeageroptions(self): """tests that a lazy relation can be upgraded to an eager relation via the options method""" -- 2.47.2