]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
rearranging mapper/objectstore into a subdirectory, breaking up files since they...
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 3 Dec 2005 06:13:09 +0000 (06:13 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 3 Dec 2005 06:13:09 +0000 (06:13 +0000)
lib/sqlalchemy/__init__.py
lib/sqlalchemy/mapping/__init__.py [new file with mode: 0644]
lib/sqlalchemy/mapping/mapper.py [new file with mode: 0644]
lib/sqlalchemy/mapping/objectstore.py [moved from lib/sqlalchemy/objectstore.py with 99% similarity]
lib/sqlalchemy/mapping/properties.py [moved from lib/sqlalchemy/mapper.py with 52% similarity]
lib/sqlalchemy/mapping/topological.py [moved from lib/sqlalchemy/topological.py with 100% similarity]
lib/sqlalchemy/util.py

index 152e1f3bd7de84a4121a0f7152c9edbd0865fe6c..7894f1777710c739897436d41ebf23c87d4805d0 100644 (file)
@@ -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 (file)
index 0000000..51e859c
--- /dev/null
@@ -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 (file)
index 0000000..8150f6f
--- /dev/null
@@ -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        
+        )
+    )
+
+
+
similarity index 99%
rename from lib/sqlalchemy/objectstore.py
rename to lib/sqlalchemy/mapping/objectstore.py
index c0250ef9645aff1c4745b2a34a9e9e5001cd62d8..f66198f2d3f422d2ff2c6ad000f6ed9af795a006 100644 (file)
@@ -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)
similarity index 52%
rename from lib/sqlalchemy/mapper.py
rename to lib/sqlalchemy/mapping/properties.py
index 7dba6222047367033432e6025a70c0b57b99c5ea..8e021efefef031e691b93e2420cfc431f918a5a0 100644 (file)
@@ -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
 # 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        )
-    )
-
index 742fd9aa722fb55d073c5adbb0cdb4efc832d0af..842d9ab3206a2e4e5f8f72610c84c3cd7ef3b542 100644 (file)
@@ -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