From: Mike Bayer Date: Sat, 3 Dec 2005 06:13:09 +0000 (+0000) Subject: rearranging mapper/objectstore into a subdirectory, breaking up files since they... X-Git-Tag: rel_0_1_0~272 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=906af22a56375a9d541bddcbd8aab2336c9ef299;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git rearranging mapper/objectstore into a subdirectory, breaking up files since they are huge --- diff --git a/lib/sqlalchemy/__init__.py b/lib/sqlalchemy/__init__.py index 152e1f3bd7..7894f17777 100644 --- a/lib/sqlalchemy/__init__.py +++ b/lib/sqlalchemy/__init__.py @@ -15,9 +15,9 @@ # along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -import sqlalchemy.mapper as mapperlib -from sqlalchemy.schema import * -from sqlalchemy.sql import * -from sqlalchemy.types import * -from sqlalchemy.mapper import * -from sqlalchemy.engine import * \ No newline at end of file +from engine import * +from types import * +from schema import * +from sql import * +import mapping as mapperlib +from mapping import * diff --git a/lib/sqlalchemy/mapping/__init__.py b/lib/sqlalchemy/mapping/__init__.py new file mode 100644 index 0000000000..51e859c510 --- /dev/null +++ b/lib/sqlalchemy/mapping/__init__.py @@ -0,0 +1,142 @@ +# mapper/__init__.py +# Copyright (C) 2005 Michael Bayer mike_mp@zzzcomputing.com +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +""" +the mapper package provides object-relational functionality, building upon the schema and sql +packages and tying operations to class properties and constructors. +""" +import sqlalchemy.sql as sql +import sqlalchemy.schema as schema +import sqlalchemy.engine as engine +import sqlalchemy.util as util +import objectstore +from mapper import * +from properties import * +import mapper as mapperlib + +__all__ = ['relation', 'eagerload', 'lazyload', 'noload', 'assignmapper', + 'mapper', 'clear_mappers', 'objectstore', 'sql', 'extension', 'class_mapper', 'object_mapper', 'MapperExtension', + 'ColumnProperty' + ] + +def relation(*args, **params): + """provides a relationship of a primary Mapper to a secondary Mapper, which corresponds + to a parent-child or associative table relationship.""" + if isinstance(args[0], type) and len(args) == 1: + return _relation_loader(*args, **params) + elif isinstance(args[0], Mapper): + return _relation_loader(*args, **params) + else: + return _relation_mapper(*args, **params) + +def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=None, lazy=True, **kwargs): + if lazy: + return LazyLoader(mapper, secondary, primaryjoin, secondaryjoin, **kwargs) + elif lazy is None: + return PropertyLoader(mapper, secondary, primaryjoin, secondaryjoin, **kwargs) + else: + return EagerLoader(mapper, secondary, primaryjoin, secondaryjoin, **kwargs) + +def _relation_mapper(class_, table=None, secondary=None, + primaryjoin=None, secondaryjoin=None, + foreignkey=None, uselist=None, private=False, live=False, association=None, lazy=True, selectalias=None, **kwargs): + + return _relation_loader(mapper(class_, table, **kwargs), secondary, primaryjoin, secondaryjoin, + foreignkey=foreignkey, uselist=uselist, private=private, live=live, association=association, lazy=lazy, selectalias=selectalias) + +class assignmapper(object): + """provides a property object that will instantiate a Mapper for a given class the first + time it is called off of the object. This is useful for attaching a Mapper to a class + that has dependencies on other classes and tables which may not have been defined yet.""" + def __init__(self, table, class_ = None, **kwargs): + self.table = table + self.kwargs = kwargs + if class_: + self.__get__(None, class_) + + def __get__(self, instance, owner): + if not hasattr(self, 'mapper'): + self.mapper = mapper(owner, self.table, **self.kwargs) + self.mapper._init_class() + if self.mapper.class_ is not owner: + raise "no match " + repr(self.mapper.class_) + " " + repr(owner) + if not hasattr(owner, 'c'): + raise "no c" + return self.mapper + +def mapper(class_, table = None, engine = None, autoload = False, *args, **params): + """returns a new or already cached Mapper object.""" + if table is None: + return class_mapper(class_) + + if isinstance(table, str): + table = schema.Table(table, engine, autoload = autoload, mustexist = not autoload) + + hashkey = mapper_hash_key(class_, table, *args, **params) + #print "HASHKEY: " + hashkey + try: + return mapper_registry[hashkey] + except KeyError: + m = Mapper(hashkey, class_, table, *args, **params) + mapper_registry.setdefault(hashkey, m) + m._init_properties() + return mapper_registry[hashkey] + +def clear_mappers(): + """removes all mappers that have been created thus far. when new mappers are + created, they will be assigned to their classes as their primary mapper.""" + mapper_registry.clear() + +def clear_mapper(m): + """removes the given mapper from the storage of mappers. when a new mapper is + created for the previous mapper's class, it will be used as that classes' + new primary mapper.""" + del mapper_registry[m.hash_key] + +def extension(ext): + """returns a MapperOption that will add the given MapperExtension to the + mapper returned by mapper.options().""" + return ExtensionOption(ext) +def eagerload(name, **kwargs): + """returns a MapperOption that will convert the property of the given name + into an eager load. Used with mapper.options()""" + return EagerLazyOption(name, toeager=True, **kwargs) + +def lazyload(name, **kwargs): + """returns a MapperOption that will convert the property of the given name + into a lazy load. Used with mapper.options()""" + return EagerLazyOption(name, toeager=False, **kwargs) + +def noload(name, **kwargs): + """returns a MapperOption that will convert the property of the given name + into a non-load. Used with mapper.options()""" + return EagerLazyOption(name, toeager=None, **kwargs) + +def object_mapper(object): + """given an object, returns the primary Mapper associated with the object + or the object's class.""" + return class_mapper(object.__class__) + +def class_mapper(class_): + """given a class, returns the primary Mapper associated with the class.""" + try: + return mapper_registry[class_._mapper] + except KeyError: + pass + except AttributeError: + pass + raise "Class '%s' has no mapper associated with it" % class_.__name__ diff --git a/lib/sqlalchemy/mapping/mapper.py b/lib/sqlalchemy/mapping/mapper.py new file mode 100644 index 0000000000..8150f6f90d --- /dev/null +++ b/lib/sqlalchemy/mapping/mapper.py @@ -0,0 +1,612 @@ +# mapper/mapper.py +# Copyright (C) 2005 Michael Bayer mike_mp@zzzcomputing.com +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +import sqlalchemy.sql as sql +import sqlalchemy.schema as schema +import sqlalchemy.engine as engine +import sqlalchemy.util as util +import objectstore + + +mapper_registry = {} + +class Mapper(object): + """Persists object instances to and from schema.Table objects via the sql package. + Instances of this class should be constructed through this package's mapper() or + relation() function.""" + def __init__(self, + hashkey, + class_, + table, + primarytable = None, + scope = "thread", + properties = None, + primary_keys = None, + is_primary = False, + inherits = None, + inherit_condition = None, + extension = None, + **kwargs): + + self.copyargs = { + 'class_':class_, + 'table':table, + 'primarytable':primarytable, + 'scope':scope, + 'properties':properties or {}, + 'primary_keys':primary_keys, + 'is_primary':False, + 'inherits':inherits, + 'inherit_condition':inherit_condition, + 'extension':extension + } + + if extension is None: + self.extension = MapperExtension() + else: + self.extension = extension + self.hashkey = hashkey + self.class_ = class_ + self.scope = scope + self.is_primary = is_primary + + if not issubclass(class_, object): + raise "Class '%s' is not a new-style class" % class_.__name__ + + if inherits is not None: + # TODO: determine inherit_condition (make JOIN do natural joins) + primarytable = inherits.primarytable + table = sql.join(table, inherits.table, inherit_condition) + + self.table = table + + # locate all tables contained within the "table" passed in, which + # may be a join or other construct + tf = TableFinder() + self.table.accept_visitor(tf) + self.tables = tf.tables + + # determine "primary" table + if primarytable is None: + if len(self.tables) > 1: + raise "table contains multiple tables - specify primary table argument to Mapper" + self.primarytable = self.tables[0] + else: + self.primarytable = primarytable + + # determine primary keys, either passed in, or get them from our set of tables + self.primary_keys = {} + if primary_keys is not None: + for k in primary_keys: + self.primary_keys.setdefault(k.table, []).append(k) + if k.table != self.table: + self.primary_keys.setdefault(self.table, []).append(k) + else: + for t in self.tables + [self.table]: + try: + list = self.primary_keys[t] + except KeyError: + list = self.primary_keys.setdefault(t, util.HashSet()) + if not len(t.primary_keys): + raise "Table " + t.name + " has no primary keys. Specify primary_keys argument to mapper." + for k in t.primary_keys: + list.append(k) + + # make table columns addressable via the mapper + self.columns = util.OrderedProperties() + self.c = self.columns + + # object attribute names mapped to MapperProperty objects + self.props = {} + + # table columns mapped to lists of MapperProperty objects + # using a list allows a single column to be defined as + # populating multiple object attributes + self.columntoproperty = {} + + # load custom properties + if properties is not None: + for key, prop in properties.iteritems(): + if isinstance(prop, schema.Column): + self.columns[key] = prop + prop = ColumnProperty(prop) + self.props[key] = prop + if isinstance(prop, ColumnProperty): + for col in prop.columns: + proplist = self.columntoproperty.setdefault(col.original, []) + proplist.append(prop) + + # load properties from the main table object, + # not overriding those set up in the 'properties' argument + for column in self.table.columns: + self.columns[column.key] = column + + if self.columntoproperty.has_key(column.original): + continue + + prop = self.props.get(column.key, None) + if prop is None: + prop = ColumnProperty(column) + self.props[column.key] = prop + elif isinstance(prop, ColumnProperty): + prop.columns.append(column) + else: + #print "WARNING: column %s not being added due to property %s" % (column.key, repr(prop)) + continue + + # its a ColumnProperty - match the ultimate table columns + # back to the property + proplist = self.columntoproperty.setdefault(column.original, []) + proplist.append(prop) + + if inherits is not None: + for key, prop in inherits.props.iteritems(): + if not self.props.has_key(key): + self.props[key] = prop._copy() + + if not hasattr(self.class_, '_mapper') or self.is_primary or not mapper_registry.has_key(self.class_._mapper) or (inherits is not None and inherits._is_primary_mapper()): + self._init_class() + + engines = property(lambda s: [t.engine for t in s.tables]) + + def add_property(self, key, prop): + self.copyargs['properties'][key] = prop + if isinstance(prop, schema.Column): + self.columns[key] = prop + prop = ColumnProperty(prop) + self.props[key] = prop + if isinstance(prop, ColumnProperty): + for col in prop.columns: + proplist = self.columntoproperty.setdefault(col.original, []) + proplist.append(prop) + prop.init(key, self) + + def _init_properties(self): + for key, prop in self.props.iteritems(): + if getattr(prop, 'key', None) is None: + prop.init(key, self) + + def __str__(self): + return "Mapper|" + self.class_.__name__ + "|" + self.primarytable.name + + def hash_key(self): + return self.hashkey + + def _is_primary_mapper(self): + return getattr(self.class_, '_mapper') == self.hashkey + + def _init_class(self): + """sets up our classes' overridden __init__ method, this mappers hash key as its + '_mapper' property, and our columns as its 'c' property. if the class already had a + mapper, the old __init__ method is kept the same.""" + if not hasattr(self.class_, '_mapper'): + oldinit = self.class_.__init__ + def init(self, *args, **kwargs): + nohist = kwargs.pop('_mapper_nohistory', False) + if oldinit is not None: + oldinit(self, *args, **kwargs) + if not nohist: + objectstore.uow().register_new(self) + self.class_.__init__ = init + self.class_._mapper = self.hashkey + self.class_.c = self.c + + def set_property(self, key, prop): + self.props[key] = prop + prop.init(key, self) + + + def instances(self, cursor, *mappers): + result = util.HistoryArraySet() + if len(mappers): + otherresults = [] + for m in mappers: + otherresults.append(util.HistoryArraySet()) + + imap = {} + while True: + row = cursor.fetchone() + if row is None: + break + self._instance(row, imap, result) + i = 0 + for m in mappers: + m._instance(row, imap, otherresults[i]) + i+=1 + + # store new stuff in the identity map + for value in imap.values(): + objectstore.uow().register_clean(value) + + if len(mappers): + return [result] + otherresults + else: + return result + + def get(self, *ident): + """returns an instance of the object based on the given identifier, or None + if not found. The *ident argument is a + list of primary keys in the order of the table def's primary keys.""" + key = objectstore.get_id_key(ident, self.class_, self.primarytable) + #print "key: " + repr(key) + " ident: " + repr(ident) + try: + return objectstore.uow()._get(key) + except KeyError: + clause = sql.and_() + i = 0 + for primary_key in self.primary_keys[self.primarytable]: + # appending to the and_'s clause list directly to skip + # typechecks etc. + clause.clauses.append(primary_key == ident[i]) + i += 1 + try: + return self.select(clause)[0] + except IndexError: + return None + + + def identity_key(self, *primary_keys): + return objectstore.get_id_key(tuple(primary_keys), self.class_, self.primarytable) + + def instance_key(self, instance): + return self.identity_key(*[self._getattrbycolumn(instance, column) for column in self.primary_keys[self.table]]) + + def compile(self, whereclause = None, **options): + """works like select, except returns the SQL statement object without + compiling or executing it""" + return self._compile(whereclause, **options) + + def options(self, *options): + """uses this mapper as a prototype for a new mapper with different behavior. + *options is a list of options directives, which include eagerload(), lazyload(), and noload()""" + + hashkey = hash_key(self) + "->" + repr([hash_key(o) for o in options]) + try: + return mapper_registry[hashkey] + except KeyError: + mapper = Mapper(hashkey, **self.copyargs) + mapper._init_properties() + + for option in options: + option.process(mapper) + return mapper_registry.setdefault(hashkey, mapper) + + def selectone(self, *args, **params): + """works like select(), but only returns the first result by itself, or None if no + objects returned.""" + ret = self.select(*args, **params) + if len(ret): + return ret[0] + else: + return None + + def select(self, arg = None, **params): + """selects instances of the object from the database. + + arg can be any ClauseElement, which will form the criterion with which to + load the objects. + + For more advanced usage, arg can also be a Select statement object, which + will be executed and its resulting rowset used to build new object instances. + in this case, the developer must insure that an adequate set of columns exists in the + rowset with which to build new object instances.""" + if arg is not None and isinstance(arg, sql.Select): + return self.select_statement(arg, **params) + else: + return self.select_whereclause(arg, **params) + + def select_whereclause(self, whereclause = None, order_by = None, **params): + statement = self._compile(whereclause, order_by = order_by) + return self.select_statement(statement, **params) + + def select_statement(self, statement, **params): + statement.use_labels = True + return self.instances(statement.execute(**params)) + + def select_text(self, text, **params): + t = sql.text(text, engine=self.primarytable.engine) + return self.instances(t.execute(**params)) + + def _getattrbycolumn(self, obj, column): + try: + prop = self.columntoproperty[column.original] + except KeyError: + try: + prop = self.props[column.key] + raise "Column '%s.%s' is not available, due to conflicting property '%s':%s" % (column.table.name, column.name, column.key, repr(prop)) + except KeyError: + raise "No column %s.%s is configured on mapper %s..." % (column.table.name, column.name, str(self)) + + return prop[0].getattr(obj) + + def _setattrbycolumn(self, obj, column, value): + self.columntoproperty[column.original][0].setattr(obj, value) + + + def save_obj(self, objects, uow): + """called by a UnitOfWork object to save objects, which involves either an INSERT or + an UPDATE statement for each table used by this mapper, for each element of the + list.""" + + for table in self.tables: + # loop thru tables in the outer loop, objects on the inner loop. + # this is important for an object represented across two tables + # so that it gets its primary keys populated for the benefit of the + # second table. + insert = [] + update = [] + + # we have our own idea of the primary keys + # for this table, in the case that the user + # specified custom primary keys. + pk = {} + for k in self.primary_keys[table]: + pk[k] = k + for obj in objects: + +# print "SAVE_OBJ we are " + hash_key(self) + " obj: " + obj.__class__.__name__ + repr(id(obj)) + params = {} + + for col in table.columns: + #if col.primary_key: + if pk.has_key(col): + if hasattr(obj, "_instance_key"): + params[col.table.name + "_" + col.key] = self._getattrbycolumn(obj, col) + else: + # its an INSERT - if its NULL, leave it out as pgsql doesnt + # like it for an autoincrement + value = self._getattrbycolumn(obj, col) + if value is not None: + params[col.key] = value + else: + params[col.key] = self._getattrbycolumn(obj, col) + + if hasattr(obj, "_instance_key"): + update.append(params) + else: + insert.append((obj, params)) + uow.register_saved_object(obj) + if len(update): + #print "REGULAR UPDATES" + clause = sql.and_() + for col in self.primary_keys[table]: + clause.clauses.append(col == sql.bindparam(col.table.name + "_" + col.key)) + statement = table.update(clause) + c = statement.execute(*update) + if table.engine.supports_sane_rowcount() and c.rowcount != len(update): + raise "ConcurrencyError - updated rowcount %d does not match number of objects updated %d" % (c.cursor.rowcount, len(update)) + if len(insert): + import sys + statement = table.insert() + for rec in insert: + (obj, params) = rec + statement.execute(**params) + primary_keys = table.engine.last_inserted_ids() + if primary_keys is not None: + i = 0 + for col in self.primary_keys[table]: + # print "col: " + table.name + "." + col.key + " val: " + repr(self._getattrbycolumn(obj, col)) + if self._getattrbycolumn(obj, col) is None: + self._setattrbycolumn(obj, col, primary_keys[i]) + i+=1 + self.extension.after_insert(self, obj) + + def delete_obj(self, objects, uow): + """called by a UnitOfWork object to delete objects, which involves a + DELETE statement for each table used by this mapper, for each object in the list.""" + for table in self.tables: + delete = [] + for obj in objects: + params = {} + if not hasattr(obj, "_instance_key"): + continue + else: + delete.append(params) + for col in self.primary_keys[table]: + params[col.key] = self._getattrbycolumn(obj, col) + uow.register_deleted_object(obj) + self.extension.before_delete(self, obj) + if len(delete): + clause = sql.and_() + for col in self.primary_keys[table]: + clause.clauses.append(col == sql.bindparam(col.key)) + statement = table.delete(clause) + c = statement.execute(*delete) + if table.engine.supports_sane_rowcount() and c.rowcount != len(delete): + raise "ConcurrencyError - updated rowcount %d does not match number of objects updated %d" % (c.cursor.rowcount, len(delete)) + + def register_dependencies(self, *args, **kwargs): + """called by an instance of objectstore.UOWTransaction to register + which mappers are dependent on which, as well as DependencyProcessor + objects which will process lists of objects in between saves and deletes.""" + for prop in self.props.values(): + prop.register_dependencies(*args, **kwargs) + + def register_deleted(self, obj, uow): + for prop in self.props.values(): + prop.register_deleted(obj, uow) + + def _compile(self, whereclause = None, order_by = None, **options): + statement = sql.select([self.table], whereclause, order_by = order_by) + statement.order_by(self.table.rowid_column) + # plugin point + for key, value in self.props.iteritems(): + value.setup(key, statement, **options) + statement.use_labels = True + return statement + + + def _identity_key(self, row): + return objectstore.get_row_key(row, self.class_, self.primarytable, self.primary_keys[self.table]) + + def _instance(self, row, imap, result = None, populate_existing = False): + """pulls an object instance from the given row and appends it to the given result + list. if the instance already exists in the given identity map, its not added. in + either case, executes all the property loaders on the instance to also process extra + information in the row.""" + + # look in main identity map. if its there, we dont do anything to it, + # including modifying any of its related items lists, as its already + # been exposed to being modified by the application. + identitykey = self._identity_key(row) + if objectstore.uow().has_key(identitykey): + instance = objectstore.uow()._get(identitykey) + + isnew = False + if populate_existing: + isnew = not imap.has_key(identitykey) + if isnew: + imap[identitykey] = instance + for prop in self.props.values(): + prop.execute(instance, row, identitykey, imap, isnew) + + if self.extension.append_result(self, row, imap, result, instance, isnew, populate_existing=populate_existing): + if result is not None: + result.append_nohistory(instance) + + return instance + + # look in result-local identitymap for it. + exists = imap.has_key(identitykey) + if not exists: + # check if primary keys in the result are None - this indicates + # an instance of the object is not present in the row + for col in self.primary_keys[self.table]: + if row[col] is None: + return None + # plugin point + instance = self.extension.create_instance(self, row, imap, self.class_) + if instance is None: + instance = self.class_(_mapper_nohistory=True) + # attach mapper hashkey to the instance ? + #instance._mapper = self.hashkey + instance._instance_key = identitykey + + imap[identitykey] = instance + isnew = True + else: + instance = imap[identitykey] + isnew = False + + + # plugin point + + # call further mapper properties on the row, to pull further + # instances from the row and possibly populate this item. + for prop in self.props.values(): + prop.execute(instance, row, identitykey, imap, isnew) + + if self.extension.append_result(self, row, imap, result, instance, isnew, populate_existing=populate_existing): + if result is not None: + result.append_nohistory(instance) + + return instance + + +class MapperProperty(object): + """an element attached to a Mapper that describes and assists in the loading and saving + of an attribute on an object instance.""" + def execute(self, instance, row, identitykey, imap, isnew): + """called when the mapper receives a row. instance is the parent instance + corresponding to the row. """ + raise NotImplementedError() + def _copy(self): + raise NotImplementedError() + def hash_key(self): + """describes this property and its instantiated arguments in such a way + as to uniquely identify the concept this MapperProperty represents,within + a process.""" + raise NotImplementedError() + def setup(self, key, statement, **options): + """called when a statement is being constructed. """ + return self + def init(self, key, parent): + """called when the MapperProperty is first attached to a new parent Mapper.""" + pass + def register_deleted(self, object, uow): + """called when the instance is being deleted""" + pass + def register_dependencies(self, *args, **kwargs): + pass + +class MapperOption(object): + """describes a modification to a Mapper in the context of making a copy + of it. This is used to assist in the prototype pattern used by mapper.options().""" + def process(self, mapper): + raise NotImplementedError() + def hash_key(self): + return repr(self) + +class ExtensionOption(MapperOption): + """adds a new MapperExtension to a mapper's chain of extensions""" + def __init__(self, ext): + self.ext = ext + def process(self, mapper): + ext.next = mapper.extension + mapper.extension = ext + +class MapperExtension(object): + def __init__(self): + self.next = None + def create_instance(self, mapper, row, imap, class_): + if self.next is None: + return None + else: + return self.next.create_instance(mapper, row, imap, class_) + def append_result(self, mapper, row, imap, result, instance, isnew, populate_existing=False): + if self.next is None: + return True + else: + return self.next.append_result(mapper, row, imap, result, instance, isnew, populate_existing) + def after_insert(self, mapper, instance): + if self.next is not None: + self.next.after_insert(mapper, instance) + def before_delete(self, mapper, instance): + if self.next is not None: + self.next.before_delete(mapper, instance) + +class TableFinder(sql.ClauseVisitor): + """given a Clause, locates all the Tables within it into a list.""" + def __init__(self): + self.tables = [] + def visit_table(self, table): + self.tables.append(table) + +def hash_key(obj): + if obj is None: + return 'None' + elif hasattr(obj, 'hash_key'): + return obj.hash_key() + else: + return repr(obj) + +def mapper_hash_key(class_, table, primarytable = None, properties = None, scope = "thread", **kwargs): + if properties is None: + properties = {} + return ( + "Mapper(%s, %s, primarytable=%s, properties=%s, scope=%s)" % ( + repr(class_), + hash_key(table), + hash_key(primarytable), + repr(dict([(k, hash_key(p)) for k,p in properties.iteritems()])), + scope + ) + ) + + + diff --git a/lib/sqlalchemy/objectstore.py b/lib/sqlalchemy/mapping/objectstore.py similarity index 99% rename from lib/sqlalchemy/objectstore.py rename to lib/sqlalchemy/mapping/objectstore.py index c0250ef964..f66198f2d3 100644 --- a/lib/sqlalchemy/objectstore.py +++ b/lib/sqlalchemy/mapping/objectstore.py @@ -24,6 +24,7 @@ import thread import sqlalchemy import sqlalchemy.util as util import sqlalchemy.attributes as attributes +import topological import weakref import string @@ -414,7 +415,7 @@ class UOWTransaction(object): mappers.append(task.mapper) bymapper[task.mapper] = task - head = util.DependencySorter(self.dependencies, mappers).sort() + head = DependencySorter(self.dependencies, mappers).sort() task = sort_hier(head) return task @@ -605,7 +606,7 @@ class UOWTask(object): #raise "hi " + repr(obj) + " " + repr(o) get_dependency_task(obj, dep).append(obj, isdelete=isdelete) - head = util.DependencySorter(tuples, allobjects).sort() + head = DependencySorter(tuples, allobjects).sort() if head is None: return None @@ -666,6 +667,9 @@ class UOWTask(object): def __repr__(self): return ("UOWTask/%d Table: '%s'" % (id(self), self.mapper and self.mapper.primarytable.name or '(none)')) + +class DependencySorter(topological.QueueDependencySorter): + pass def mapper(*args, **params): return sqlalchemy.mapperlib.mapper(*args, **params) diff --git a/lib/sqlalchemy/mapper.py b/lib/sqlalchemy/mapping/properties.py similarity index 52% rename from lib/sqlalchemy/mapper.py rename to lib/sqlalchemy/mapping/properties.py index 7dba622204..8e021efefe 100644 --- a/lib/sqlalchemy/mapper.py +++ b/lib/sqlalchemy/mapping/properties.py @@ -1,4 +1,4 @@ -# mapper.py +# properties.py # Copyright (C) 2005 Michael Bayer mike_mp@zzzcomputing.com # # This library is free software; you can redistribute it and/or @@ -15,650 +15,15 @@ # along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -""" -the mapper package provides object-relational functionality, building upon the schema and sql -packages and tying operations to class properties and constructors. -""" + +from mapper import * import sqlalchemy.sql as sql import sqlalchemy.schema as schema import sqlalchemy.engine as engine import sqlalchemy.util as util -import sqlalchemy.objectstore as objectstore -import random, copy, types - -__all__ = ['relation', 'eagerload', 'lazyload', 'noload', 'assignmapper', - 'mapper', 'clear_mappers', 'objectstore', 'sql', 'extension', 'class_mapper', 'object_mapper', 'MapperExtension', - 'ColumnProperty' - ] - -def relation(*args, **params): - """provides a relationship of a primary Mapper to a secondary Mapper, which corresponds - to a parent-child or associative table relationship.""" - if isinstance(args[0], type) and len(args) == 1: - return _relation_loader(*args, **params) - elif isinstance(args[0], Mapper): - return _relation_loader(*args, **params) - else: - return _relation_mapper(*args, **params) - -def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=None, lazy=True, **kwargs): - if lazy: - return LazyLoader(mapper, secondary, primaryjoin, secondaryjoin, **kwargs) - elif lazy is None: - return PropertyLoader(mapper, secondary, primaryjoin, secondaryjoin, **kwargs) - else: - return EagerLoader(mapper, secondary, primaryjoin, secondaryjoin, **kwargs) - -def _relation_mapper(class_, table=None, secondary=None, - primaryjoin=None, secondaryjoin=None, - foreignkey=None, uselist=None, private=False, live=False, association=None, lazy=True, selectalias=None, **kwargs): - - return _relation_loader(mapper(class_, table, **kwargs), secondary, primaryjoin, secondaryjoin, - foreignkey=foreignkey, uselist=uselist, private=private, live=live, association=association, lazy=lazy, selectalias=selectalias) - -class assignmapper(object): - """provides a property object that will instantiate a Mapper for a given class the first - time it is called off of the object. This is useful for attaching a Mapper to a class - that has dependencies on other classes and tables which may not have been defined yet.""" - def __init__(self, table, class_ = None, **kwargs): - self.table = table - self.kwargs = kwargs - if class_: - self.__get__(None, class_) - - def __get__(self, instance, owner): - if not hasattr(self, 'mapper'): - self.mapper = mapper(owner, self.table, **self.kwargs) - self.mapper._init_class() - if self.mapper.class_ is not owner: - raise "no match " + repr(self.mapper.class_) + " " + repr(owner) - if not hasattr(owner, 'c'): - raise "no c" - return self.mapper - -_mappers = {} -def mapper(class_, table = None, engine = None, autoload = False, *args, **params): - """returns a new or already cached Mapper object.""" - if table is None: - return class_mapper(class_) - - if isinstance(table, str): - table = schema.Table(table, engine, autoload = autoload, mustexist = not autoload) - - hashkey = mapper_hash_key(class_, table, *args, **params) - #print "HASHKEY: " + hashkey - try: - return _mappers[hashkey] - except KeyError: - m = Mapper(hashkey, class_, table, *args, **params) - _mappers.setdefault(hashkey, m) - m._init_properties() - return _mappers[hashkey] - -def clear_mappers(): - """removes all mappers that have been created thus far. when new mappers are - created, they will be assigned to their classes as their primary mapper.""" - _mappers.clear() - -def clear_mapper(m): - """removes the given mapper from the storage of mappers. when a new mapper is - created for the previous mapper's class, it will be used as that classes' - new primary mapper.""" - del _mappers[m.hash_key] - -def extension(ext): - """returns a MapperOption that will add the given MapperExtension to the - mapper returned by mapper.options().""" - return ExtensionOption(ext) -def eagerload(name, **kwargs): - """returns a MapperOption that will convert the property of the given name - into an eager load. Used with mapper.options()""" - return EagerLazyOption(name, toeager=True, **kwargs) - -def lazyload(name, **kwargs): - """returns a MapperOption that will convert the property of the given name - into a lazy load. Used with mapper.options()""" - return EagerLazyOption(name, toeager=False, **kwargs) - -def noload(name, **kwargs): - """returns a MapperOption that will convert the property of the given name - into a non-load. Used with mapper.options()""" - return EagerLazyOption(name, toeager=None, **kwargs) - -def object_mapper(object): - """given an object, returns the primary Mapper associated with the object - or the object's class.""" - return class_mapper(object.__class__) - -def class_mapper(class_): - """given a class, returns the primary Mapper associated with the class.""" - try: - return _mappers[class_._mapper] - except KeyError: - pass - except AttributeError: - pass - raise "Class '%s' has no mapper associated with it" % class_.__name__ - - -class Mapper(object): - """Persists object instances to and from schema.Table objects via the sql package. - Instances of this class should be constructed through this package's mapper() or - relation() function.""" - def __init__(self, - hashkey, - class_, - table, - primarytable = None, - scope = "thread", - properties = None, - primary_keys = None, - is_primary = False, - inherits = None, - inherit_condition = None, - extension = None, - **kwargs): - - self.copyargs = { - 'class_':class_, - 'table':table, - 'primarytable':primarytable, - 'scope':scope, - 'properties':properties or {}, - 'primary_keys':primary_keys, - 'is_primary':False, - 'inherits':inherits, - 'inherit_condition':inherit_condition, - 'extension':extension - } - - if extension is None: - self.extension = MapperExtension() - else: - self.extension = extension - self.hashkey = hashkey - self.class_ = class_ - self.scope = scope - self.is_primary = is_primary - - if not issubclass(class_, object): - raise "Class '%s' is not a new-style class" % class_.__name__ - - if inherits is not None: - # TODO: determine inherit_condition (make JOIN do natural joins) - primarytable = inherits.primarytable - table = sql.join(table, inherits.table, inherit_condition) - - self.table = table - - # locate all tables contained within the "table" passed in, which - # may be a join or other construct - tf = TableFinder() - self.table.accept_visitor(tf) - self.tables = tf.tables - - # determine "primary" table - if primarytable is None: - if len(self.tables) > 1: - raise "table contains multiple tables - specify primary table argument to Mapper" - self.primarytable = self.tables[0] - else: - self.primarytable = primarytable - - # determine primary keys, either passed in, or get them from our set of tables - self.primary_keys = {} - if primary_keys is not None: - for k in primary_keys: - self.primary_keys.setdefault(k.table, []).append(k) - if k.table != self.table: - self.primary_keys.setdefault(self.table, []).append(k) - else: - for t in self.tables + [self.table]: - try: - list = self.primary_keys[t] - except KeyError: - list = self.primary_keys.setdefault(t, util.HashSet()) - if not len(t.primary_keys): - raise "Table " + t.name + " has no primary keys. Specify primary_keys argument to mapper." - for k in t.primary_keys: - list.append(k) - - # make table columns addressable via the mapper - self.columns = util.OrderedProperties() - self.c = self.columns - - # object attribute names mapped to MapperProperty objects - self.props = {} - - # table columns mapped to lists of MapperProperty objects - # using a list allows a single column to be defined as - # populating multiple object attributes - self.columntoproperty = {} - - # load custom properties - if properties is not None: - for key, prop in properties.iteritems(): - if isinstance(prop, schema.Column): - self.columns[key] = prop - prop = ColumnProperty(prop) - self.props[key] = prop - if isinstance(prop, ColumnProperty): - for col in prop.columns: - proplist = self.columntoproperty.setdefault(col.original, []) - proplist.append(prop) - - # load properties from the main table object, - # not overriding those set up in the 'properties' argument - for column in self.table.columns: - self.columns[column.key] = column - - if self.columntoproperty.has_key(column.original): - continue - - prop = self.props.get(column.key, None) - if prop is None: - prop = ColumnProperty(column) - self.props[column.key] = prop - elif isinstance(prop, ColumnProperty): - prop.columns.append(column) - else: - #print "WARNING: column %s not being added due to property %s" % (column.key, repr(prop)) - continue - - # its a ColumnProperty - match the ultimate table columns - # back to the property - proplist = self.columntoproperty.setdefault(column.original, []) - proplist.append(prop) - - if inherits is not None: - for key, prop in inherits.props.iteritems(): - if not self.props.has_key(key): - self.props[key] = prop._copy() - - if not hasattr(self.class_, '_mapper') or self.is_primary or not _mappers.has_key(self.class_._mapper) or (inherits is not None and inherits._is_primary_mapper()): - self._init_class() - - engines = property(lambda s: [t.engine for t in s.tables]) - - def add_property(self, key, prop): - self.copyargs['properties'][key] = prop - if isinstance(prop, schema.Column): - self.columns[key] = prop - prop = ColumnProperty(prop) - self.props[key] = prop - if isinstance(prop, ColumnProperty): - for col in prop.columns: - proplist = self.columntoproperty.setdefault(col.original, []) - proplist.append(prop) - prop.init(key, self) - - def _init_properties(self): - for key, prop in self.props.iteritems(): - if getattr(prop, 'key', None) is None: - prop.init(key, self) - - def __str__(self): - return "Mapper|" + self.class_.__name__ + "|" + self.primarytable.name - - def hash_key(self): - return self.hashkey - - def _is_primary_mapper(self): - return getattr(self.class_, '_mapper') == self.hashkey - - def _init_class(self): - """sets up our classes' overridden __init__ method, this mappers hash key as its - '_mapper' property, and our columns as its 'c' property. if the class already had a - mapper, the old __init__ method is kept the same.""" - if not hasattr(self.class_, '_mapper'): - oldinit = self.class_.__init__ - def init(self, *args, **kwargs): - nohist = kwargs.pop('_mapper_nohistory', False) - if oldinit is not None: - oldinit(self, *args, **kwargs) - if not nohist: - objectstore.uow().register_new(self) - self.class_.__init__ = init - self.class_._mapper = self.hashkey - self.class_.c = self.c - - def set_property(self, key, prop): - self.props[key] = prop - prop.init(key, self) - - - def instances(self, cursor, *mappers): - result = util.HistoryArraySet() - if len(mappers): - otherresults = [] - for m in mappers: - otherresults.append(util.HistoryArraySet()) - - imap = {} - while True: - row = cursor.fetchone() - if row is None: - break - self._instance(row, imap, result) - i = 0 - for m in mappers: - m._instance(row, imap, otherresults[i]) - i+=1 - - # store new stuff in the identity map - for value in imap.values(): - objectstore.uow().register_clean(value) - - if len(mappers): - return [result] + otherresults - else: - return result - - def get(self, *ident): - """returns an instance of the object based on the given identifier, or None - if not found. The *ident argument is a - list of primary keys in the order of the table def's primary keys.""" - key = objectstore.get_id_key(ident, self.class_, self.primarytable) - #print "key: " + repr(key) + " ident: " + repr(ident) - try: - return objectstore.uow()._get(key) - except KeyError: - clause = sql.and_() - i = 0 - for primary_key in self.primary_keys[self.primarytable]: - # appending to the and_'s clause list directly to skip - # typechecks etc. - clause.clauses.append(primary_key == ident[i]) - i += 1 - try: - return self.select(clause)[0] - except IndexError: - return None - - - def identity_key(self, *primary_keys): - return objectstore.get_id_key(tuple(primary_keys), self.class_, self.primarytable) - - def instance_key(self, instance): - return self.identity_key(*[self._getattrbycolumn(instance, column) for column in self.primary_keys[self.table]]) - - def compile(self, whereclause = None, **options): - """works like select, except returns the SQL statement object without - compiling or executing it""" - return self._compile(whereclause, **options) - - def options(self, *options): - """uses this mapper as a prototype for a new mapper with different behavior. - *options is a list of options directives, which include eagerload(), lazyload(), and noload()""" - - hashkey = hash_key(self) + "->" + repr([hash_key(o) for o in options]) - try: - return _mappers[hashkey] - except KeyError: - mapper = Mapper(hashkey, **self.copyargs) - mapper._init_properties() - - for option in options: - option.process(mapper) - return _mappers.setdefault(hashkey, mapper) - - def selectone(self, *args, **params): - """works like select(), but only returns the first result by itself, or None if no - objects returned.""" - ret = self.select(*args, **params) - if len(ret): - return ret[0] - else: - return None - - def select(self, arg = None, **params): - """selects instances of the object from the database. - - arg can be any ClauseElement, which will form the criterion with which to - load the objects. - - For more advanced usage, arg can also be a Select statement object, which - will be executed and its resulting rowset used to build new object instances. - in this case, the developer must insure that an adequate set of columns exists in the - rowset with which to build new object instances.""" - if arg is not None and isinstance(arg, sql.Select): - return self.select_statement(arg, **params) - else: - return self.select_whereclause(arg, **params) - - def select_whereclause(self, whereclause = None, order_by = None, **params): - statement = self._compile(whereclause, order_by = order_by) - return self.select_statement(statement, **params) - - def select_statement(self, statement, **params): - statement.use_labels = True - return self.instances(statement.execute(**params)) - - def select_text(self, text, **params): - t = sql.text(text, engine=self.primarytable.engine) - return self.instances(t.execute(**params)) - - def _getattrbycolumn(self, obj, column): - try: - prop = self.columntoproperty[column.original] - except KeyError: - try: - prop = self.props[column.key] - raise "Column '%s.%s' is not available, due to conflicting property '%s':%s" % (column.table.name, column.name, column.key, repr(prop)) - except KeyError: - raise "No column %s.%s is configured on mapper %s..." % (column.table.name, column.name, str(self)) - - return prop[0].getattr(obj) - - def _setattrbycolumn(self, obj, column, value): - self.columntoproperty[column.original][0].setattr(obj, value) - - - def save_obj(self, objects, uow): - """called by a UnitOfWork object to save objects, which involves either an INSERT or - an UPDATE statement for each table used by this mapper, for each element of the - list.""" - - for table in self.tables: - # loop thru tables in the outer loop, objects on the inner loop. - # this is important for an object represented across two tables - # so that it gets its primary keys populated for the benefit of the - # second table. - insert = [] - update = [] - - # we have our own idea of the primary keys - # for this table, in the case that the user - # specified custom primary keys. - pk = {} - for k in self.primary_keys[table]: - pk[k] = k - for obj in objects: - -# print "SAVE_OBJ we are " + hash_key(self) + " obj: " + obj.__class__.__name__ + repr(id(obj)) - params = {} - - for col in table.columns: - #if col.primary_key: - if pk.has_key(col): - if hasattr(obj, "_instance_key"): - params[col.table.name + "_" + col.key] = self._getattrbycolumn(obj, col) - else: - # its an INSERT - if its NULL, leave it out as pgsql doesnt - # like it for an autoincrement - value = self._getattrbycolumn(obj, col) - if value is not None: - params[col.key] = value - else: - params[col.key] = self._getattrbycolumn(obj, col) - - if hasattr(obj, "_instance_key"): - update.append(params) - else: - insert.append((obj, params)) - uow.register_saved_object(obj) - if len(update): - #print "REGULAR UPDATES" - clause = sql.and_() - for col in self.primary_keys[table]: - clause.clauses.append(col == sql.bindparam(col.table.name + "_" + col.key)) - statement = table.update(clause) - c = statement.execute(*update) - if table.engine.supports_sane_rowcount() and c.rowcount != len(update): - raise "ConcurrencyError - updated rowcount %d does not match number of objects updated %d" % (c.cursor.rowcount, len(update)) - if len(insert): - import sys - statement = table.insert() - for rec in insert: - (obj, params) = rec - statement.execute(**params) - primary_keys = table.engine.last_inserted_ids() - if primary_keys is not None: - i = 0 - for col in self.primary_keys[table]: - # print "col: " + table.name + "." + col.key + " val: " + repr(self._getattrbycolumn(obj, col)) - if self._getattrbycolumn(obj, col) is None: - self._setattrbycolumn(obj, col, primary_keys[i]) - i+=1 - self.extension.after_insert(self, obj) - - def delete_obj(self, objects, uow): - """called by a UnitOfWork object to delete objects, which involves a - DELETE statement for each table used by this mapper, for each object in the list.""" - for table in self.tables: - delete = [] - for obj in objects: - params = {} - if not hasattr(obj, "_instance_key"): - continue - else: - delete.append(params) - for col in self.primary_keys[table]: - params[col.key] = self._getattrbycolumn(obj, col) - uow.register_deleted_object(obj) - self.extension.before_delete(self, obj) - if len(delete): - clause = sql.and_() - for col in self.primary_keys[table]: - clause.clauses.append(col == sql.bindparam(col.key)) - statement = table.delete(clause) - c = statement.execute(*delete) - if table.engine.supports_sane_rowcount() and c.rowcount != len(delete): - raise "ConcurrencyError - updated rowcount %d does not match number of objects updated %d" % (c.cursor.rowcount, len(delete)) - - def register_dependencies(self, *args, **kwargs): - """called by an instance of objectstore.UOWTransaction to register - which mappers are dependent on which, as well as DependencyProcessor - objects which will process lists of objects in between saves and deletes.""" - for prop in self.props.values(): - prop.register_dependencies(*args, **kwargs) - - def register_deleted(self, obj, uow): - for prop in self.props.values(): - prop.register_deleted(obj, uow) - - def _compile(self, whereclause = None, order_by = None, **options): - statement = sql.select([self.table], whereclause, order_by = order_by) - statement.order_by(self.table.rowid_column) - # plugin point - for key, value in self.props.iteritems(): - value.setup(key, statement, **options) - statement.use_labels = True - return statement - - - def _identity_key(self, row): - return objectstore.get_row_key(row, self.class_, self.primarytable, self.primary_keys[self.table]) - - def _instance(self, row, imap, result = None, populate_existing = False): - """pulls an object instance from the given row and appends it to the given result - list. if the instance already exists in the given identity map, its not added. in - either case, executes all the property loaders on the instance to also process extra - information in the row.""" - - # look in main identity map. if its there, we dont do anything to it, - # including modifying any of its related items lists, as its already - # been exposed to being modified by the application. - identitykey = self._identity_key(row) - if objectstore.uow().has_key(identitykey): - instance = objectstore.uow()._get(identitykey) - - isnew = False - if populate_existing: - isnew = not imap.has_key(identitykey) - if isnew: - imap[identitykey] = instance - for prop in self.props.values(): - prop.execute(instance, row, identitykey, imap, isnew) - - if self.extension.append_result(self, row, imap, result, instance, isnew, populate_existing=populate_existing): - if result is not None: - result.append_nohistory(instance) - - return instance - - # look in result-local identitymap for it. - exists = imap.has_key(identitykey) - if not exists: - # check if primary keys in the result are None - this indicates - # an instance of the object is not present in the row - for col in self.primary_keys[self.table]: - if row[col] is None: - return None - # plugin point - instance = self.extension.create_instance(self, row, imap, self.class_) - if instance is None: - instance = self.class_(_mapper_nohistory=True) - # attach mapper hashkey to the instance ? - #instance._mapper = self.hashkey - instance._instance_key = identitykey - - imap[identitykey] = instance - isnew = True - else: - instance = imap[identitykey] - isnew = False - - - # plugin point - - # call further mapper properties on the row, to pull further - # instances from the row and possibly populate this item. - for prop in self.props.values(): - prop.execute(instance, row, identitykey, imap, isnew) - - if self.extension.append_result(self, row, imap, result, instance, isnew, populate_existing=populate_existing): - if result is not None: - result.append_nohistory(instance) - - return instance - - -class MapperProperty(object): - """an element attached to a Mapper that describes and assists in the loading and saving - of an attribute on an object instance.""" - def execute(self, instance, row, identitykey, imap, isnew): - """called when the mapper receives a row. instance is the parent instance - corresponding to the row. """ - raise NotImplementedError() - def _copy(self): - raise NotImplementedError() - def hash_key(self): - """describes this property and its instantiated arguments in such a way - as to uniquely identify the concept this MapperProperty represents,within - a process.""" - raise NotImplementedError() - def setup(self, key, statement, **options): - """called when a statement is being constructed. """ - return self - def init(self, key, parent): - """called when the MapperProperty is first attached to a new parent Mapper.""" - pass - def register_deleted(self, object, uow): - """called when the instance is being deleted""" - pass - def register_dependencies(self, *args, **kwargs): - pass +import mapper +import objectstore +import random class ColumnProperty(MapperProperty): """describes an object attribute that corresponds to a table column.""" @@ -676,7 +41,7 @@ class ColumnProperty(MapperProperty): def _copy(self): return ColumnProperty(*self.columns) - + def init(self, key, parent): self.key = key # establish a SmartProperty property manager on the object for this key @@ -687,7 +52,8 @@ class ColumnProperty(MapperProperty): def execute(self, instance, row, identitykey, imap, isnew): if isnew: instance.__dict__[self.key] = row[self.columns[0]] - + +mapper.ColumnProperty = ColumnProperty class PropertyLoader(MapperProperty): LEFT = 0 @@ -1252,22 +618,6 @@ class EagerLoader(PropertyLoader): row = fakerow return self.mapper._instance(row, imap, result_list) -class MapperOption(object): - """describes a modification to a Mapper in the context of making a copy - of it. This is used to assist in the prototype pattern used by mapper.options().""" - def process(self, mapper): - raise NotImplementedError() - def hash_key(self): - return repr(self) - -class ExtensionOption(MapperOption): - """adds a new MapperExtension to a mapper's chain of extensions""" - def __init__(self, ext): - self.ext = ext - def process(self, mapper): - ext.next = mapper.extension - mapper.extension = ext - class EagerLazyOption(MapperOption): """an option that switches a PropertyLoader to be an EagerLoader or LazyLoader""" def __init__(self, key, toeager = True, **kwargs): @@ -1289,14 +639,14 @@ class EagerLazyOption(MapperOption): submapper = submapper.options(EagerLazyOption(tup[1], self.toeager)) else: submapper = oldprop.mapper - + if self.toeager: class_ = EagerLoader elif self.toeager is None: class_ = PropertyLoader else: class_ = LazyLoader - + self.kwargs.setdefault('primaryjoin', oldprop.primaryjoin) self.kwargs.setdefault('secondaryjoin', oldprop.secondaryjoin) self.kwargs.setdefault('foreignkey', oldprop.foreignkey) @@ -1316,14 +666,14 @@ class Aliasizer(sql.ClauseVisitor): self.binary = None self.match = False self.aliases = kwargs.get('aliases', {}) - + def get_alias(self, table): try: return self.aliases[table] except: aliasname = table.name + "_" + hex(random.randint(0, 65535))[2:] return self.aliases.setdefault(table, sql.alias(table, aliasname)) - + def visit_binary(self, binary): if isinstance(binary.left, schema.Column) and self.tables.has_key(binary.left.table): binary.left = self.get_alias(binary.left.table).c[binary.left.name] @@ -1332,57 +682,8 @@ class Aliasizer(sql.ClauseVisitor): binary.right = self.get_alias(binary.right.table).c[binary.right.name] self.match = True -class TableFinder(sql.ClauseVisitor): - """given a Clause, locates all the Tables within it into a list.""" - def __init__(self): - self.tables = [] - def visit_table(self, table): - self.tables.append(table) - class BinaryVisitor(sql.ClauseVisitor): def __init__(self, func): self.func = func def visit_binary(self, binary): self.func(binary) - - -class MapperExtension(object): - def __init__(self): - self.next = None - def create_instance(self, mapper, row, imap, class_): - if self.next is None: - return None - else: - return self.next.create_instance(mapper, row, imap, class_) - def append_result(self, mapper, row, imap, result, instance, isnew, populate_existing=False): - if self.next is None: - return True - else: - return self.next.append_result(mapper, row, imap, result, instance, isnew, populate_existing) - def after_insert(self, mapper, instance): - if self.next is not None: - self.next.after_insert(mapper, instance) - def before_delete(self, mapper, instance): - if self.next is not None: - self.next.before_delete(mapper, instance) - -def hash_key(obj): - if obj is None: - return 'None' - elif hasattr(obj, 'hash_key'): - return obj.hash_key() - else: - return repr(obj) - -def mapper_hash_key(class_, table, primarytable = None, properties = None, scope = "thread", **kwargs): - if properties is None: - properties = {} - return ( - "Mapper(%s, %s, primarytable=%s, properties=%s, scope=%s)" % ( - repr(class_), - hash_key(table), - hash_key(primarytable), - repr(dict([(k, hash_key(p)) for k,p in properties.iteritems()])), - scope ) - ) - diff --git a/lib/sqlalchemy/topological.py b/lib/sqlalchemy/mapping/topological.py similarity index 100% rename from lib/sqlalchemy/topological.py rename to lib/sqlalchemy/mapping/topological.py diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py index 742fd9aa72..842d9ab320 100644 --- a/lib/sqlalchemy/util.py +++ b/lib/sqlalchemy/util.py @@ -17,7 +17,6 @@ __all__ = ['OrderedProperties', 'OrderedDict'] import thread, weakref, UserList,string -import sqlalchemy.topological as topological class OrderedProperties(object): """an object that maintains the order in which attributes are set upon it. @@ -334,5 +333,3 @@ class ScopedRegistry(object): def _clear_application(self): self.application = createfunc() -class DependencySorter(topological.QueueDependencySorter): - pass