From: Mike Bayer Date: Sat, 2 Jun 2007 19:06:13 +0000 (+0000) Subject: - removed cascade_mappers(). long overdue. X-Git-Tag: rel_0_4_6~227 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=65f0edb94943e5e97bb44c71b15a541084353eae;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - removed cascade_mappers(). long overdue. - removed sqlalchemy.orm from sqlalchemy.__init__ namespace. still needs updates in documentation, tutorial pages - moved MapperExtension to interfaces package - moved ExtensionCarrier to orm.util --- diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index 22fb10ffd9..0d8f670b94 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -7,7 +7,6 @@ from sqlalchemy.types import * from sqlalchemy.sql import * from sqlalchemy.schema import * -from sqlalchemy.orm import * from sqlalchemy.engine import create_engine from sqlalchemy.schema import default_metadata diff --git a/lib/sqlalchemy/ext/assignmapper.py b/lib/sqlalchemy/ext/assignmapper.py index 7886f4d272..23c6cc9ae9 100644 --- a/lib/sqlalchemy/ext/assignmapper.py +++ b/lib/sqlalchemy/ext/assignmapper.py @@ -1,5 +1,6 @@ -from sqlalchemy import mapper, util, Query, exceptions +from sqlalchemy import util, exceptions import types +from sqlalchemy.orm import mapper, Query def monkeypatch_query_method(ctx, class_, name): def do(self, *args, **kwargs): diff --git a/lib/sqlalchemy/ext/sessioncontext.py b/lib/sqlalchemy/ext/sessioncontext.py index 5a0e0afc77..385c034ddf 100644 --- a/lib/sqlalchemy/ext/sessioncontext.py +++ b/lib/sqlalchemy/ext/sessioncontext.py @@ -1,6 +1,5 @@ from sqlalchemy.util import ScopedRegistry -from sqlalchemy.orm.mapper import MapperExtension, EXT_PASS -from sqlalchemy.orm import create_session +from sqlalchemy.orm import create_session, MapperExtension, EXT_PASS __all__ = ['SessionContext', 'SessionContextExt'] diff --git a/lib/sqlalchemy/mods/threadlocal.py b/lib/sqlalchemy/mods/threadlocal.py index c8043bc624..292479e7b6 100644 --- a/lib/sqlalchemy/mods/threadlocal.py +++ b/lib/sqlalchemy/mods/threadlocal.py @@ -19,7 +19,7 @@ installed will reference this global context when creating new mapped object instances. """ -from sqlalchemy import util, engine, mapper +from sqlalchemy import util, engine from sqlalchemy.ext.sessioncontext import SessionContext import sqlalchemy.ext.assignmapper as assignmapper from sqlalchemy.orm.mapper import global_extensions diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index 83a31eee24..eeefe1d755 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -11,17 +11,18 @@ packages and tying operations to class properties and constructors. from sqlalchemy import exceptions from sqlalchemy import util as sautil -from sqlalchemy.orm.mapper import * +from sqlalchemy.orm.mapper import Mapper, object_mapper, class_mapper, mapper_registry +from sqlalchemy.orm.interfaces import SynonymProperty, MapperExtension, EXT_PASS, ExtensionOption +from sqlalchemy.orm.properties import PropertyLoader, ColumnProperty, BackRef from sqlalchemy.orm import mapper as mapperlib from sqlalchemy.orm.query import Query from sqlalchemy.orm.util import polymorphic_union -from sqlalchemy.orm import properties, strategies, interfaces from sqlalchemy.orm.session import Session as create_session from sqlalchemy.orm.session import object_session, attribute_manager __all__ = ['relation', 'column_property', 'backref', 'eagerload', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', 'undefer_group', 'extension', 'mapper', 'clear_mappers', 'compile_mappers', 'clear_mapper', 'class_mapper', 'object_mapper', 'MapperExtension', 'Query', - 'cascade_mappers', 'polymorphic_union', 'create_session', 'synonym', 'contains_alias', 'contains_eager', 'EXT_PASS', 'object_session' + 'polymorphic_union', 'create_session', 'synonym', 'contains_alias', 'contains_eager', 'EXT_PASS', 'object_session' ] def relation(*args, **kwargs): @@ -59,10 +60,10 @@ def column_property(*args, **kwargs): no direct correspondence to the mapped selectable will effectively be non-persisted attributes. """ - return properties.ColumnProperty(*args, **kwargs) + return ColumnProperty(*args, **kwargs) def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=None, lazy=True, **kwargs): - return properties.PropertyLoader(mapper, secondary, primaryjoin, secondaryjoin, lazy=lazy, **kwargs) + return PropertyLoader(mapper, secondary, primaryjoin, secondaryjoin, lazy=lazy, **kwargs) def backref(name, **kwargs): """Create a BackRef object with explicit arguments, which are the same arguments one @@ -72,7 +73,7 @@ def backref(name, **kwargs): place of a string argument. """ - return properties.BackRef(name, **kwargs) + return BackRef(name, **kwargs) def deferred(*columns, **kwargs): """Return a ``DeferredColumnProperty``, which indicates this @@ -82,7 +83,7 @@ def deferred(*columns, **kwargs): Used with the `properties` dictionary sent to ``mapper()``. """ - return properties.ColumnProperty(deferred=True, *columns, **kwargs) + return ColumnProperty(deferred=True, *columns, **kwargs) def mapper(class_, table=None, *args, **params): """Return a new ``Mapper`` object. @@ -98,7 +99,7 @@ def synonym(name, proxy=False): Used with the `properties` dictionary sent to ``mapper()``. """ - return interfaces.SynonymProperty(name, proxy=proxy) + return SynonymProperty(name, proxy=proxy) def compile_mappers(): """Compile all mappers that have been defined. @@ -249,63 +250,3 @@ def undefer_group(name): """ return strategies.UndeferGroupOption(name) -def cascade_mappers(*classes_or_mappers): - """Attempt to create a series of ``relations()`` between mappers - automatically, via introspecting the foreign key relationships of - the underlying tables. - - Given a list of classes and/or mappers, identify the foreign key - relationships between the given mappers or corresponding class - mappers, and create ``relation()`` objects representing those - relationships, including a backreference. Attempt to find the - *secondary* table in a many-to-many relationship as well. - - The names of the relations will be a lowercase version of the - related class. In the case of one-to-many or many-to-many, the - name will be *pluralized*, which currently is based on the English - language (i.e. an 's' or 'es' added to it). - - NOTE: this method usually works poorly, and its usage is generally - not advised. - """ - - table_to_mapper = {} - for item in classes_or_mappers: - if isinstance(item, Mapper): - m = item - else: - klass = item - m = class_mapper(klass) - table_to_mapper[m.mapped_table] = m - - def pluralize(name): - # oh crap, do we need locale stuff now - if name[-1] == 's': - return name + "es" - else: - return name + "s" - - for table,mapper in table_to_mapper.iteritems(): - for fk in table.foreign_keys: - if fk.column.table is table: - continue - secondary = None - try: - m2 = table_to_mapper[fk.column.table] - except KeyError: - if len(fk.column.table.primary_key): - continue - for sfk in fk.column.table.foreign_keys: - if sfk.column.table is table: - continue - m2 = table_to_mapper.get(sfk.column.table) - secondary = fk.column.table - if m2 is None: - continue - if secondary: - propname = pluralize(m2.class_.__name__.lower()) - propname2 = pluralize(mapper.class_.__name__.lower()) - else: - propname = m2.class_.__name__.lower() - propname2 = pluralize(mapper.class_.__name__.lower()) - mapper.add_property(propname, relation(m2, secondary=secondary, backref=propname2)) diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index c5da017322..caa5e4412a 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -7,6 +7,198 @@ from sqlalchemy import util, logging +# returned by a MapperExtension method to indicate a "do nothing" response +EXT_PASS = 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. + """ + + + def init_instance(self, mapper, class_, instance, args, kwargs): + return EXT_PASS + + def init_failed(self, mapper, class_, instance, args, kwargs): + return EXT_PASS + + def get_session(self): + """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 + + 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. + """ + + return EXT_PASS + + 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. + """ + + return EXT_PASS + + 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. + """ + + return EXT_PASS + + 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. + """ + + return EXT_PASS + + 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. + """ + + return EXT_PASS + + + def translate_row(self, mapper, context, row): + """Perform pre-processing on the given result row and return a + new row instance. + + This is called as the very first step in the ``_instance()`` + method. + """ + + return EXT_PASS + + def create_instance(self, mapper, selectcontext, row, class_): + """Receive a row when a new object instance is about to be + created from that row. + + The method can choose to create the instance itself, or it can + return None to indicate normal object creation should take + place. + + mapper + The mapper doing the operation + + selectcontext + SelectionContext corresponding to the instances() call + + row + The result row from the database + + class\_ + The class we are mapping. + """ + + return EXT_PASS + + 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 + 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 + desired. + + mapper + The mapper doing the operation. + + selectcontext + SelectionContext corresponding to the instances() call. + + row + The result row from the database. + + instance + The object instance to be appended to the result. + + result + List to which results are being appended. + + \**flags + extra information about the row, same as criterion in + `create_row_processor()` method of [sqlalchemy.orm.interfaces#MapperProperty] + """ + + return EXT_PASS + + def populate_instance(self, mapper, selectcontext, row, instance, **flags): + """Receive a newly-created instance before that instance has + its attributes populated. + + 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 + 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 + + def before_insert(self, mapper, connection, instance): + """Receive an object instance before that instance is INSERTed + into its table. + + This is a good place to set up primary key values and such + that aren't handled otherwise. + """ + + return EXT_PASS + + def before_update(self, mapper, connection, instance): + """Receive an object instance before that instance is UPDATEed.""" + + return EXT_PASS + + 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 + + def before_delete(self, mapper, connection, instance): + """Receive an object instance before that instance is DELETEed.""" + + return EXT_PASS + + def after_delete(self, mapper, connection, instance): + """Receive an object instance after that instance is DELETEed.""" + + return EXT_PASS + class MapperProperty(object): """Manage the relationship of a ``Mapper`` to a single class attribute, as well as that attribute as it appears on individual @@ -75,6 +267,9 @@ class MapperProperty(object): def set_parent(self, parent): self.parent = parent + def get_sub_mapper(self): + raise NotImplementedError() + def init(self, key, parent): """Called after all mappers are compiled to assemble relationships between mappers, establish instrumented class @@ -123,33 +318,7 @@ class MapperProperty(object): raise NotImplementedError() -class SynonymProperty(MapperProperty): - def __init__(self, name, proxy=False): - self.name = name - self.proxy = proxy - - def setup(self, querycontext, **kwargs): - pass - def create_row_processor(self, selectcontext, mapper, row): - return (None, None) - - def do_init(self): - if not self.proxy: - return - class SynonymProp(object): - def __set__(s, obj, value): - setattr(obj, self.name, value) - def __delete__(s, obj): - delattr(obj, self.name) - def __get__(s, obj, owner): - if obj is None: - return s - return getattr(obj, self.name) - setattr(self.parent.class_, self.key, SynonymProp()) - - def merge(self, session, source, dest, _recursive): - pass class StrategizedProperty(MapperProperty): """A MapperProperty which uses selectable strategies to affect @@ -218,6 +387,46 @@ class MapperOption(object): def process_query(self, query): pass +class ExtensionOption(MapperOption): + """a MapperOption that applies a MapperExtension to a query operation.""" + + def __init__(self, ext): + self.ext = ext + + def process_query(self, query): + query.extension.append(self.ext) + +class SynonymProperty(MapperProperty): + def __init__(self, name, proxy=False): + self.name = name + self.proxy = proxy + + def setup(self, querycontext, **kwargs): + pass + + def get_sub_mapper(self): + return self.parent.props[self.name].get_sub_mapper() + + def create_row_processor(self, selectcontext, mapper, row): + return (None, None) + + def do_init(self): + if not self.proxy: + return + class SynonymProp(object): + def __set__(s, obj, value): + setattr(obj, self.name, value) + def __delete__(s, obj): + delattr(obj, self.name) + def __get__(s, obj, owner): + if obj is None: + return s + return getattr(obj, self.name) + setattr(self.parent.class_, self.key, SynonymProp()) + + def merge(self, session, source, dest, _recursive): + 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. diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index c24de84bf5..525028b019 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -7,11 +7,12 @@ from sqlalchemy import sql, schema, util, exceptions, logging from sqlalchemy import sql_util as sqlutil from sqlalchemy.orm import util as mapperutil +from sqlalchemy.orm.util import ExtensionCarrier from sqlalchemy.orm import sync -from sqlalchemy.orm.interfaces import MapperProperty, MapperOption, OperationContext +from sqlalchemy.orm.interfaces import MapperProperty, MapperOption, OperationContext, EXT_PASS, MapperExtension import weakref -__all__ = ['Mapper', 'MapperExtension', 'class_mapper', 'object_mapper', 'EXT_PASS', 'mapper_registry', 'ExtensionOption'] +__all__ = ['Mapper', 'class_mapper', 'object_mapper', 'mapper_registry'] # a dictionary mapping classes to their primary mappers mapper_registry = weakref.WeakKeyDictionary() @@ -24,8 +25,6 @@ global_extensions = [] # column NO_ATTRIBUTE = object() -# returned by a MapperExtension method to indicate a "do nothing" response -EXT_PASS = object() # lock used to synchronize the "mapper compile" step _COMPILE_MUTEX = util.threading.Lock() @@ -409,7 +408,7 @@ class Mapper(object): for ext_obj in util.to_list(extension): extlist.add(ext_obj) - self.extension = _ExtensionCarrier() + self.extension = ExtensionCarrier() for ext in extlist: self.extension.append(ext) @@ -1609,248 +1608,7 @@ class Mapper(object): Mapper.logger = logging.class_logger(Mapper) -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. - """ - - def init_instance(self, mapper, class_, instance, args, kwargs): - return EXT_PASS - - def init_failed(self, mapper, class_, instance, args, kwargs): - return EXT_PASS - - def get_session(self): - """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 - - 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. - """ - - return EXT_PASS - - 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. - """ - - return EXT_PASS - - 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. - """ - - return EXT_PASS - - 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. - """ - - return EXT_PASS - - 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. - """ - - return EXT_PASS - - - def translate_row(self, mapper, context, row): - """Perform pre-processing on the given result row and return a - new row instance. - - This is called as the very first step in the ``_instance()`` - method. - """ - - return EXT_PASS - - def create_instance(self, mapper, selectcontext, row, class_): - """Receive a row when a new object instance is about to be - created from that row. - - The method can choose to create the instance itself, or it can - return None to indicate normal object creation should take - place. - - mapper - The mapper doing the operation - - selectcontext - SelectionContext corresponding to the instances() call - - row - The result row from the database - - class\_ - The class we are mapping. - """ - - return EXT_PASS - - 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 - 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 - desired. - - mapper - The mapper doing the operation. - - selectcontext - SelectionContext corresponding to the instances() call. - - row - The result row from the database. - - instance - The object instance to be appended to the result. - - result - List to which results are being appended. - - \**flags - extra information about the row, same as criterion in - `create_row_processor()` method of [sqlalchemy.orm.interfaces#MapperProperty] - """ - - return EXT_PASS - - def populate_instance(self, mapper, selectcontext, row, instance, **flags): - """Receive a newly-created instance before that instance has - its attributes populated. - - 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 - 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 - - def before_insert(self, mapper, connection, instance): - """Receive an object instance before that instance is INSERTed - into its table. - - This is a good place to set up primary key values and such - that aren't handled otherwise. - """ - - return EXT_PASS - - def before_update(self, mapper, connection, instance): - """Receive an object instance before that instance is UPDATEed.""" - - return EXT_PASS - - 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 - - def before_delete(self, mapper, connection, instance): - """Receive an object instance before that instance is DELETEed.""" - - return EXT_PASS - def after_delete(self, mapper, connection, instance): - """Receive an object instance after that instance is DELETEed.""" - - return EXT_PASS - -class _ExtensionCarrier(MapperExtension): - def __init__(self): - self.__elements = [] - - def __iter__(self): - return iter(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) - - def _create_do(funcname): - def _do(self, *args, **kwargs): - for elem in self.__elements: - ret = getattr(elem, funcname)(*args, **kwargs) - if ret is not EXT_PASS: - return ret - else: - return EXT_PASS - return _do - - init_instance = _create_do('init_instance') - init_failed = _create_do('init_failed') - dispose_class = _create_do('dispose_class') - get_session = _create_do('get_session') - load = _create_do('load') - get = _create_do('get') - get_by = _create_do('get_by') - select_by = _create_do('select_by') - select = _create_do('select') - translate_row = _create_do('translate_row') - create_instance = _create_do('create_instance') - append_result = _create_do('append_result') - populate_instance = _create_do('populate_instance') - before_insert = _create_do('before_insert') - before_update = _create_do('before_update') - after_update = _create_do('after_update') - after_insert = _create_do('after_insert') - before_delete = _create_do('before_delete') - after_delete = _create_do('after_delete') - - -class ExtensionOption(MapperOption): - def __init__(self, ext): - self.ext = ext - - def process_query(self, query): - query.extension.append(self.ext) class ClassKey(object): """Key a class and an entity name to a mapper, via the mapper_registry.""" diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 5e65fffb38..b5b8f83069 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -18,6 +18,8 @@ from sqlalchemy.orm import util as mapperutil import sets, random from sqlalchemy.orm.interfaces import * +__all__ = ['ColumnProperty', 'PropertyLoader', 'BackRef'] + class ColumnProperty(StrategizedProperty): """Describes an object attribute that corresponds to a table column.""" @@ -38,6 +40,9 @@ class ColumnProperty(StrategizedProperty): else: return strategies.ColumnLoader(self) + def get_sub_mapper(self): + return None + def getattr(self, object): return getattr(object, self.key) @@ -57,6 +62,8 @@ ColumnProperty.logger = logging.class_logger(ColumnProperty) mapper.ColumnProperty = ColumnProperty + + class PropertyLoader(StrategizedProperty): """Describes an object property that holds a single item or list of items that correspond to a related database table. @@ -109,6 +116,9 @@ class PropertyLoader(StrategizedProperty): private = property(lambda s:s.cascade.delete_orphan) + def get_sub_mapper(self): + return self.mapper + def create_strategy(self): if self.lazy: return strategies.LazyLoader(self) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 81dbe07b8a..2a93bd5d22 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -7,6 +7,7 @@ from sqlalchemy import sql, util, exceptions, sql_util, logging, schema from sqlalchemy.orm import mapper, class_mapper, object_mapper from sqlalchemy.orm.interfaces import OperationContext, SynonymProperty +from sqlalchemy.orm.util import ExtensionCarrier __all__ = ['Query', 'QueryContext', 'SelectionContext'] @@ -22,7 +23,7 @@ class Query(object): self.select_mapper = self.mapper.get_select_mapper().compile() self.always_refresh = kwargs.pop('always_refresh', self.mapper.always_refresh) self.lockmode = lockmode - self.extension = mapper._ExtensionCarrier() + self.extension = ExtensionCarrier() if extension is not None: self.extension.append(extension) self.extension.append(self.mapper.extension) @@ -59,9 +60,7 @@ class Query(object): q._session = self.session q.is_polymorphic = self.is_polymorphic q.lockmode = self.lockmode - q.extension = mapper._ExtensionCarrier() - for ext in self.extension: - q.extension.append(ext) + q.extension = self.extension.copy() q._offset = self._offset q._limit = self._limit q._group_by = self._group_by diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index 6b35855989..a3184d62b5 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -7,7 +7,7 @@ """sqlalchemy.orm.interfaces.LoaderStrategy implementations, and related MapperOptions.""" from sqlalchemy import sql, schema, util, exceptions, sql_util, logging -from sqlalchemy.orm import mapper, query +from sqlalchemy.orm import mapper from sqlalchemy.orm.interfaces import * from sqlalchemy.orm.attributes import InstrumentedAttribute from sqlalchemy.orm import session as sessionlib @@ -237,6 +237,7 @@ class LazyLoader(AbstractRelationLoader): # determine if our "lazywhere" clause is the same as the mapper's # get() clause. then we can just use mapper.get() + from sqlalchemy.orm import query self.use_get = not self.uselist and query.Query(self.mapper)._get_clause.compare(self.lazywhere) if self.use_get: self.logger.info(str(self.parent_property) + " will use query.get() to optimize instance loads") diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index e80d954bd9..a148dc6afa 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -5,6 +5,7 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php from sqlalchemy import sql, util, exceptions +from sqlalchemy.orm.interfaces import MapperExtension, EXT_PASS all_cascades = util.Set(["delete", "delete-orphan", "all", "merge", "expunge", "save-update", "refresh-expire", "none"]) @@ -109,6 +110,56 @@ class TranslatingDict(dict): def setdefault(self, col, value): return super(TranslatingDict, self).setdefault(self.__translate_col(col), value) +class ExtensionCarrier(MapperExtension): + def __init__(self, _elements=None): + self.__elements = _elements or [] + + def copy(self): + return ExtensionCarrier(list(self.__elements)) + + def __iter__(self): + return iter(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) + + def _create_do(funcname): + def _do(self, *args, **kwargs): + for elem in self.__elements: + ret = getattr(elem, funcname)(*args, **kwargs) + if ret is not EXT_PASS: + return ret + else: + return EXT_PASS + return _do + + init_instance = _create_do('init_instance') + init_failed = _create_do('init_failed') + dispose_class = _create_do('dispose_class') + get_session = _create_do('get_session') + load = _create_do('load') + get = _create_do('get') + get_by = _create_do('get_by') + select_by = _create_do('select_by') + select = _create_do('select') + translate_row = _create_do('translate_row') + create_instance = _create_do('create_instance') + append_result = _create_do('append_result') + populate_instance = _create_do('populate_instance') + before_insert = _create_do('before_insert') + before_update = _create_do('before_update') + after_update = _create_do('after_update') + after_insert = _create_do('after_insert') + before_delete = _create_do('before_delete') + after_delete = _create_do('after_delete') + class BinaryVisitor(sql.ClauseVisitor): def __init__(self, func): self.func = func diff --git a/test/orm/association.py b/test/orm/association.py index 755f6cf890..c751ac8a17 100644 --- a/test/orm/association.py +++ b/test/orm/association.py @@ -1,6 +1,7 @@ import testbase from sqlalchemy import * +from sqlalchemy.orm import * class AssociationTest(testbase.PersistTest): diff --git a/test/orm/cascade.py b/test/orm/cascade.py index 7cb1231608..80b846c682 100644 --- a/test/orm/cascade.py +++ b/test/orm/cascade.py @@ -3,6 +3,7 @@ import unittest, sys, datetime from sqlalchemy.ext.sessioncontext import SessionContext from sqlalchemy import * +from sqlalchemy.orm import * class O2MCascadeTest(testbase.AssertMixin): def tearDown(self): diff --git a/test/orm/compile.py b/test/orm/compile.py index 96e56e5972..604c75d488 100644 --- a/test/orm/compile.py +++ b/test/orm/compile.py @@ -1,5 +1,7 @@ import testbase from sqlalchemy import * +from sqlalchemy.orm import * + class CompileTest(testbase.AssertMixin): """test various mapper compilation scenarios""" diff --git a/test/orm/cycles.py b/test/orm/cycles.py index d02a8c8d2d..dc086b635b 100644 --- a/test/orm/cycles.py +++ b/test/orm/cycles.py @@ -1,6 +1,7 @@ from testbase import PersistTest, AssertMixin, ORMTest import unittest, sys, os from sqlalchemy import * +from sqlalchemy.orm import * import StringIO import testbase diff --git a/test/orm/eagertest1.py b/test/orm/eagertest1.py index 9765379f4b..da94c782ab 100644 --- a/test/orm/eagertest1.py +++ b/test/orm/eagertest1.py @@ -2,6 +2,7 @@ from testbase import PersistTest, AssertMixin import testbase import unittest, sys, os from sqlalchemy import * +from sqlalchemy.orm import * import datetime class EagerTest(AssertMixin): diff --git a/test/orm/eagertest2.py b/test/orm/eagertest2.py index 78e1d98707..e7bc4c2cb0 100644 --- a/test/orm/eagertest2.py +++ b/test/orm/eagertest2.py @@ -2,6 +2,7 @@ from testbase import PersistTest, AssertMixin import testbase import unittest, sys, os from sqlalchemy import * +from sqlalchemy.orm import * import datetime from sqlalchemy.ext.sessioncontext import SessionContext diff --git a/test/orm/eagertest3.py b/test/orm/eagertest3.py index c92444dd61..0a55a1b56a 100644 --- a/test/orm/eagertest3.py +++ b/test/orm/eagertest3.py @@ -1,6 +1,7 @@ from testbase import PersistTest, AssertMixin import testbase from sqlalchemy import * +from sqlalchemy.orm import * from sqlalchemy.ext.selectresults import SelectResults import random diff --git a/test/orm/entity.py b/test/orm/entity.py index 202964a698..4fc15fef16 100644 --- a/test/orm/entity.py +++ b/test/orm/entity.py @@ -1,6 +1,7 @@ from testbase import PersistTest, AssertMixin import unittest from sqlalchemy import * +from sqlalchemy.orm import * import testbase from sqlalchemy.ext.sessioncontext import SessionContext diff --git a/test/orm/generative.py b/test/orm/generative.py index bda34ba1c8..a64277e2ef 100644 --- a/test/orm/generative.py +++ b/test/orm/generative.py @@ -3,6 +3,7 @@ import testbase import tables from sqlalchemy import * +from sqlalchemy.orm import * from sqlalchemy import exceptions class Foo(object): diff --git a/test/orm/inheritance/abc_inheritance.py b/test/orm/inheritance/abc_inheritance.py index 89a7cfc3e5..d85ac39c71 100644 --- a/test/orm/inheritance/abc_inheritance.py +++ b/test/orm/inheritance/abc_inheritance.py @@ -1,4 +1,6 @@ from sqlalchemy import * +from sqlalchemy.orm import * + from sqlalchemy.orm.sync import ONETOMANY, MANYTOONE import testbase diff --git a/test/orm/inheritance/basic.py b/test/orm/inheritance/basic.py index f95390008e..d8ae774081 100644 --- a/test/orm/inheritance/basic.py +++ b/test/orm/inheritance/basic.py @@ -1,5 +1,6 @@ import testbase from sqlalchemy import * +from sqlalchemy.orm import * class O2MTest(testbase.ORMTest): diff --git a/test/orm/inheritance/concrete.py b/test/orm/inheritance/concrete.py index 9f4e275ae2..af5b7da0ea 100644 --- a/test/orm/inheritance/concrete.py +++ b/test/orm/inheritance/concrete.py @@ -1,4 +1,5 @@ from sqlalchemy import * +from sqlalchemy.orm import * import testbase class ConcreteTest1(testbase.ORMTest): diff --git a/test/orm/inheritance/magazine.py b/test/orm/inheritance/magazine.py index 9a52919dd7..20270d59cb 100644 --- a/test/orm/inheritance/magazine.py +++ b/test/orm/inheritance/magazine.py @@ -1,5 +1,7 @@ import testbase from sqlalchemy import * +from sqlalchemy.orm import * + class BaseObject(object): def __init__(self, *args, **kwargs): diff --git a/test/orm/inheritance/manytomany.py b/test/orm/inheritance/manytomany.py index b5cc83e7b4..9571ccf2fc 100644 --- a/test/orm/inheritance/manytomany.py +++ b/test/orm/inheritance/manytomany.py @@ -1,5 +1,6 @@ import testbase from sqlalchemy import * +from sqlalchemy.orm import * class InheritTest(testbase.ORMTest): diff --git a/test/orm/inheritance/poly_linked_list.py b/test/orm/inheritance/poly_linked_list.py index 30cda4bb6a..406981e7a4 100644 --- a/test/orm/inheritance/poly_linked_list.py +++ b/test/orm/inheritance/poly_linked_list.py @@ -1,5 +1,6 @@ import testbase from sqlalchemy import * +from sqlalchemy.orm import * class PolymorphicCircularTest(testbase.ORMTest): keep_mappers = True diff --git a/test/orm/inheritance/polymorph.py b/test/orm/inheritance/polymorph.py index 1ff608d24e..7b14378bc5 100644 --- a/test/orm/inheritance/polymorph.py +++ b/test/orm/inheritance/polymorph.py @@ -1,5 +1,7 @@ import testbase from sqlalchemy import * +from sqlalchemy.orm import * + import sets # tests basic polymorphic mapper loading/saving, minimal relations diff --git a/test/orm/inheritance/polymorph2.py b/test/orm/inheritance/polymorph2.py index bd95d4ff23..fbcdb5131a 100644 --- a/test/orm/inheritance/polymorph2.py +++ b/test/orm/inheritance/polymorph2.py @@ -1,4 +1,6 @@ from sqlalchemy import * +from sqlalchemy.orm import * + import testbase class AttrSettable(object): diff --git a/test/orm/inheritance/productspec.py b/test/orm/inheritance/productspec.py index 9065264566..510f9ec8b6 100644 --- a/test/orm/inheritance/productspec.py +++ b/test/orm/inheritance/productspec.py @@ -1,5 +1,7 @@ import testbase from sqlalchemy import * +from sqlalchemy.orm import * + from datetime import datetime class InheritTest(testbase.ORMTest): diff --git a/test/orm/inheritance/single.py b/test/orm/inheritance/single.py index d78084fe11..87ee0e57c0 100644 --- a/test/orm/inheritance/single.py +++ b/test/orm/inheritance/single.py @@ -1,4 +1,6 @@ from sqlalchemy import * +from sqlalchemy.orm import * + import testbase class SingleInheritanceTest(testbase.AssertMixin): diff --git a/test/orm/lazytest1.py b/test/orm/lazytest1.py index 9f2d53e9d0..0a88785cda 100644 --- a/test/orm/lazytest1.py +++ b/test/orm/lazytest1.py @@ -2,6 +2,7 @@ from testbase import PersistTest, AssertMixin import testbase import unittest, sys, os from sqlalchemy import * +from sqlalchemy.orm import * import datetime class LazyTest(AssertMixin): diff --git a/test/orm/manytomany.py b/test/orm/manytomany.py index f6e9197a2c..01df54d816 100644 --- a/test/orm/manytomany.py +++ b/test/orm/manytomany.py @@ -1,5 +1,6 @@ import testbase from sqlalchemy import * +from sqlalchemy.orm import * import string class Place(object): diff --git a/test/orm/mapper.py b/test/orm/mapper.py index 0b8ca19910..34d0c81e47 100644 --- a/test/orm/mapper.py +++ b/test/orm/mapper.py @@ -2,6 +2,7 @@ from testbase import PersistTest, AssertMixin import testbase import unittest, sys, os from sqlalchemy import * +from sqlalchemy.orm import * import sqlalchemy.exceptions as exceptions from sqlalchemy.ext.sessioncontext import SessionContext, SessionContextExt from tables import * diff --git a/test/orm/memusage.py b/test/orm/memusage.py index b456a1abbc..e53890d69f 100644 --- a/test/orm/memusage.py +++ b/test/orm/memusage.py @@ -1,4 +1,5 @@ from sqlalchemy import * +from sqlalchemy.orm import * from sqlalchemy.orm import mapperlib, session, unitofwork, attributes Mapper = mapperlib.Mapper import gc diff --git a/test/orm/merge.py b/test/orm/merge.py index cca01f2a56..41a755afb4 100644 --- a/test/orm/merge.py +++ b/test/orm/merge.py @@ -1,6 +1,7 @@ from testbase import PersistTest, AssertMixin import testbase from sqlalchemy import * +from sqlalchemy.orm import * from tables import * import tables diff --git a/test/orm/onetoone.py b/test/orm/onetoone.py index 525c8db7e8..e78eddb093 100644 --- a/test/orm/onetoone.py +++ b/test/orm/onetoone.py @@ -1,5 +1,6 @@ import testbase from sqlalchemy import * +from sqlalchemy.orm import * from sqlalchemy.ext.sessioncontext import SessionContext class Jack(object): diff --git a/test/orm/relationships.py b/test/orm/relationships.py index 5f53af080f..e5fd0dd328 100644 --- a/test/orm/relationships.py +++ b/test/orm/relationships.py @@ -5,6 +5,7 @@ db = testbase.db #db. from sqlalchemy import * +from sqlalchemy.orm import * class RelationTest(testbase.PersistTest): diff --git a/test/orm/session.py b/test/orm/session.py index d659d834bd..cf0b6d5d8a 100644 --- a/test/orm/session.py +++ b/test/orm/session.py @@ -7,6 +7,7 @@ from tables import * db = testbase.db from sqlalchemy import * +from sqlalchemy.orm import * class SessionTest(AssertMixin): diff --git a/test/orm/sessioncontext.py b/test/orm/sessioncontext.py index 83bc2f2bf9..57615ed583 100644 --- a/test/orm/sessioncontext.py +++ b/test/orm/sessioncontext.py @@ -3,6 +3,8 @@ import unittest, sys, os from sqlalchemy.ext.sessioncontext import SessionContext from sqlalchemy.orm.session import object_session, Session from sqlalchemy import * +from sqlalchemy.orm import * + import testbase metadata = MetaData() diff --git a/test/orm/unitofwork.py b/test/orm/unitofwork.py index 62b1db8604..e89c0f9a85 100644 --- a/test/orm/unitofwork.py +++ b/test/orm/unitofwork.py @@ -1,5 +1,6 @@ from testbase import PersistTest, AssertMixin from sqlalchemy import * +from sqlalchemy.orm import * import testbase import pickleable from sqlalchemy.orm.mapper import global_extensions