[Checkins] SVN: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/ initial import

Andreas Jung andreas at andreas-jung.com
Sat Mar 17 04:13:34 EDT 2007


Log message for revision 73269:
  initial import
  

Changed:
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/TODO.txt
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/__init__.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/base.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/header.txt
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/interfaces.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/mapper.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/model.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/postgres.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/test.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/__init__.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/framework.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/testSQLAlchemy.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/util.py
  A   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/zope_mixin.py

-=-
Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/TODO.txt
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/TODO.txt	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/TODO.txt	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,7 @@
+- move ZopeWrapper into base.py
+
+- refactor Zope wrapper as mixin-class
+
+- ZopePostgresWrapper get two base classes (PythonPostgresWrapper, ZopeWrapperMixin)
+
+

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/__init__.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/__init__.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/__init__.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,12 @@
+##########################################################################
+# z3c.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Freiburg, Germany
+# (C) 2007, ZOPYX Ltd. & Co. KG, Tuebingen, Germany
+# 
+# Published under der Zope Public License V 2.1 
+##########################################################################
+
+
+from util import *
+from model import Model

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/base.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/base.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/base.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,104 @@
+##########################################################################
+# z3c.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Freiburg, Germany
+# (C) 2007, ZOPYX Ltd. & Co. KG, Tuebingen, Germany
+# 
+# Published under der Zope Public License V 2.1 
+##########################################################################
+
+import threading
+
+import sqlalchemy
+from sqlalchemy.engine.url import make_url
+
+from zope.interface import implements
+from zope.component import getUtility
+from zope.component.interfaces import ComponentLookupError
+
+from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper, IModelProvider
+from z3c.sqlalchemy.model import Model
+from z3c.sqlalchemy.odict import OrderedDict
+from z3c.sqlalchemy.mapper import MapperFactory, LazyMapperCollection
+
+import transaction
+
+
+_cache = threading.local() # module-level cache 
+
+marker = object
+
+
+class BaseWrapper(object):
+
+    implements(ISQLAlchemyWrapper)
+
+    def __init__(self, dsn, model=None, echo=False):
+        """ 'dsn' - an RFC-1738-style connection string
+
+            'model' - optional instance of model.Model
+
+            'echo' - output generated SQL commands
+        """
+
+        self.dsn = dsn
+        self.url = make_url(dsn)
+        self.host = self.url.host
+        self.port = self.url.port
+        self.username = self.url.username
+        self.password = self.url.password
+        self.dbname = self.url.database 
+        self.drivername = self.url.drivername
+        self.echo = echo
+        self._engine = self._createEngine()
+        self._engine.echo = echo
+        self._model = None
+
+
+        if model:
+            if isinstance(model, Model):
+                self._model = model
+
+            elif isinstance(model, basestring):
+                try:
+                    util = getUtility(IModelProvider, model)
+                except ComponentLookupError:
+                    raise ComponentLookupError("No named utility '%s' implementing IModelProvider found" % model)
+
+
+                self._model = util.getModel()
+
+            else:
+                raise ValueError("The 'model' parameter passed to constructor must either be "\
+                                 "the name of a named utility implementing IModelProvider or "\
+                                 "an instance of haufe.sqlalchemy.model.Model.")
+
+        # mappers must be initialized at last since we need to acces
+        # the 'model' from within the constructor of LazyMapperCollection
+        self._mappers = LazyMapperCollection(self)
+
+
+    @property
+    def metadata(self):
+        return sqlalchemy.BoundMetaData(self._engine)
+
+    @property
+    def session(self):
+        return sqlalchemy.create_session(self._engine)
+
+    def getMapper(self, tablename, schema='public'):
+        return self._mappers.getMapper(tablename, schema)
+
+    @property
+    def engine(self):
+        """ only for private purposes! """
+        return self._engine
+
+    @property
+    def model(self):
+        """ only for private purposes! """
+        return self._model
+
+    def _createEngine(self):
+        return sqlalchemy.create_engine(self.dsn)
+

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/header.txt
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/header.txt	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/header.txt	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,9 @@
+##########################################################################
+# z3c.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Freiburg, Germany
+# (C) 2007, ZOPYX Ltd. & Co. KG, Tuebingen, Germany
+# 
+# Published under der Zope Public License V 2.1 
+##########################################################################
+

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/interfaces.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/interfaces.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/interfaces.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,67 @@
+##########################################################################
+# z3c.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Freiburg, Germany
+# (C) 2007, ZOPYX Ltd. & Co. KG, Tuebingen, Germany
+# 
+# Published under der Zope Public License V 2.1 
+##########################################################################
+
+
+from zope.interface import Interface
+from zope.schema import Int, TextLine, List, Dict, Bool
+
+class ISQLAlchemyWrapper(Interface):
+    """ A SQLAlchemyWrapper wraps sqlalchemy and deals with
+        connection and transaction handling.
+    """
+
+    dsn = TextLine(title=u'A RFC-1738 style connection string',
+                  required=True)
+
+    dbname = TextLine(title=u'Database name',
+                  required=True)
+
+    host = TextLine(title=u'Hostname of database',
+                required=True)
+
+    port = Int(title=u'Port of database',
+               required=True)
+
+    username = TextLine(title=u'Database user',
+                    required=True)
+
+    password = TextLine(title=u'Password of database user',
+                    required=True)
+
+    echo = Bool(title=u'Echo all SQL statements to the console',
+                required=True)
+
+    # Object() isn't available in Zope 3.0
+#    metadata = Object(title=u'Computed attribute representing a MetaData instance')
+
+#    session = Object(title=u'Computed attributed (creates a new sqlalchemy.Session')
+
+
+    def getMapper(tablename, schema='public'):
+        """ return a mapper class for a table given by its
+            'tablename' and an optional 'schema' name
+        """                
+
+
+
+
+class IModelProvider(Interface):
+    """ A model providers provides information about the tables to be used
+        and the mapper classes.
+    """
+
+    def getModel():
+        """ The model is described as an ordered dictionary.
+            The entries are (tablename, some_dict) where 'some_dict' is a
+            dictionary containing a key 'table' referencing a Table() instance and an
+            optional key 'relationships' referencing a sequence of related table names. An
+            optional mapper class can be specified through the 'class' key (otherwise a
+            default mapper class will be autogenerated).
+        """
+

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/mapper.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/mapper.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/mapper.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,139 @@
+##########################################################################
+# z3c.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Freiburg, Germany
+# (C) 2007, ZOPYX Ltd. & Co. KG, Tuebingen, Germany
+# 
+# Published under der Zope Public License V 2.1 
+##########################################################################
+
+"""
+Utility methods for SqlAlchemy
+"""
+
+import new
+import odict
+import threading
+
+from sqlalchemy import Table, mapper, BoundMetaData, relation
+
+marker = object
+
+
+class MappedClassBase(object):
+    """ base class for all mapped classes """
+
+    def __init__(self, **kw):
+        """ accepts keywords arguments used for initialization of
+            mapped attributes/columns.
+        """
+
+        for k,v in kw.items():
+            setattr(self, k, v)
+
+
+class MapperFactory(object):
+    """ a factory for table and mapper objects """
+
+    def __init__(self, metadata):
+        self.metadata = metadata
+
+    def __call__(self, table, properties={}, cls=None):
+        """ Returns a tuple (mapped_class, table_class).
+            'table' - sqlalchemy.Table to be mapped
+            'properties' - dict containing additional informations about
+                           relationships etc (see sqlalchemy.Mapper docs)
+            'cls' - (optional) class used as base for creating the mapper 
+                    class (will be autogenerated if not available).
+        """ 
+
+        if cls is None:
+            newCls = new.classobj('_mapped_%s' % table.name, (MappedClassBase,), {})
+        else:
+            newCls = cls
+
+        mapper(newCls, table, properties=properties)
+        return newCls
+
+
+
+class LazyMapperCollection(odict.OrderedDict):
+    """ Implements a cache for table mappers """
+
+    def __init__(self, wrapper):
+        super(LazyMapperCollection, self).__init__()
+        self._wrapper = wrapper
+        self._engine = wrapper.engine
+        self._model = wrapper.model or {}
+        self._metadata = BoundMetaData(self._engine)
+        self._mapper_factory = MapperFactory(self._metadata)
+        self._dependent_tables = None
+        self._lock = threading.Lock()
+
+
+    def getMapper(self, name, schema='public'):
+        """ return a (cached) mapper class for a given table 'name' """
+
+        if self._dependent_tables is None:
+            # introspect table dependencies once
+
+            if hasattr(self._wrapper, 'findDependentTables'):
+                self._dependent_tables = self._wrapper.findDependentTables()
+            else:
+                self._dependent_tables = {}
+
+
+        if not self.has_key(name):
+
+
+            # no-cached data, let's lookup the table ourselfs
+            table = None
+
+            # check if the optional model provides a table definition
+            if self._model.has_key(name):            
+                table = self._model[name].get('table')
+
+            # if not: introspect table definition
+            if table is None:
+                table = Table(name, self._metadata, autoload=True)
+
+            # check if the model contains an optional mapper class
+            mapper_class = None
+            if self._model.has_key(name):            
+                mapper_class = self._model[name].get('mapper_class')
+
+
+            # use auto-introspected table dependencies for creating
+            # the 'properties' dict that tells the mapper about
+            # relationships to other tables 
+
+            dependent_table_names = []
+            if self._model.has_key(name):
+
+                if self._model[name].get('relations') != None:
+                    dependent_table_names = self._model[name].get('relations', []) or []
+                elif self._model[name].get('autodetect_relations', False) == True:
+                    dependent_table_names = self._dependent_tables.get(name, []) or []
+                
+            # build additional property dict for mapper
+            properties = {}
+
+            # find all dependent tables (referencing the current table)
+            for table_refname in dependent_table_names:
+
+                # create or get a mapper for the referencing table
+                table_ref_mapper = self.getMapper(table_refname)
+
+                # add the mapper as relation to the properties dict
+                properties[table_refname] = relation(table_ref_mapper)
+       
+            # create a mapper and cache it 
+            self._lock.acquire()
+            self[name] = self._mapper_factory(table, 
+                                              properties=properties, 
+                                              cls=mapper_class)
+
+            self._lock.release()
+
+        return self[name]
+        

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/model.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/model.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/model.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,93 @@
+##########################################################################
+# z3c.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Freiburg, Germany
+# (C) 2007, ZOPYX Ltd. & Co. KG, Tuebingen, Germany
+# 
+# Published under der Zope Public License V 2.1 
+##########################################################################
+
+"""
+Optional Model support 
+"""
+
+
+import sqlalchemy
+from odict import OrderedDict
+
+__all__ = ('Model',)
+
+
+class Model(OrderedDict):
+    """ The Model is an optional helper class that can be passed to the
+        constructor of a SQLAlchemy wrapper in order to provide hints for the mapper
+        generation.
+    """        
+
+    def __init__(self, *args):
+        """ The constructor can be called with a series of dict. Each dict
+            represents a single table and its data (see add() method).
+        """
+
+        super(Model, self).__init__()
+
+        for d in args:
+            self.add(**d)
+
+
+    def add(self, name, table=None, mapper_class=None, relations=None, autodetect_relations=False):
+        """ 'name'  -- name of table (no schema support so far!)
+
+            'table' -- a sqlalchemy.Table instance (None, for autoloading)
+
+            'mapper_class' -- an optional class to be used as mapper class for 'table'
+
+            'relations' -- an optional list of table names referencing 'table'. This is used 
+                           for auto-constructing the relation properties of the mapper class.
+
+            'autodetect_relations' -- try to autodetect the relationships between tables
+                           and auto-construct the relation properties of the mapper if
+                           'relations is omitted'
+        """
+
+        if table is not None and not isinstance(table, sqlalchemy.Table):
+            raise TypeError("'table' must be an instance or sqlalchemy.Table or None")
+
+        if mapper_class is not None and not issubclass(mapper_class, object):
+            raise TypeError("'mapper_class' must be a new-style class")
+        
+        if relations is not None:
+            for r in relations:
+                if not isinstance(r, str):
+                    raise TypeError('relations must be specified as sequence of strings')    
+
+        if relations is not None and autodetect_relations == True:
+            raise ValueError("'relations' and 'autodetect_relations' can't be specified at the same time")
+
+        self[name] = {'name' : name,
+                      'table' : table,
+                      'relations' : relations,
+                      'mapper_class' : mapper_class,
+                      'autodetect_relations' : autodetect_relations
+                     }
+
+                        
+
+if __name__ == '__main__':
+
+    m = Model()
+    md = sqlalchemy.MetaData()
+    m.add('users')
+    m.add('groups', sqlalchemy.Table('groups', md,
+                                     sqlalchemy.Column('id', sqlalchemy.Integer),
+                                    ))
+    m = Model()
+    md = sqlalchemy.MetaData()
+    m = Model({'name' : 'users'},
+              {'name' : 'groups',
+               'table' : sqlalchemy.Table('groups', md,
+                                           sqlalchemy.Column('id', sqlalchemy.Integer),
+                                         ),
+              },
+             )
+    

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/postgres.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/postgres.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/postgres.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,73 @@
+##########################################################################
+# z3c.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Freiburg, Germany
+# (C) 2007, ZOPYX Ltd. & Co. KG, Tuebingen, Germany
+# 
+# Published under der Zope Public License V 2.1 
+##########################################################################
+
+
+import sys
+import threading
+
+import sqlalchemy
+
+from zope.interface import implements
+
+from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper
+from z3c.sqlalchemy.base import BaseWrapper
+
+from zope_mixin import ZopeMixin
+
+class PythonPostgresWrapper(BaseWrapper):
+
+    implements(ISQLAlchemyWrapper)
+
+    def findDependentTables(self, schema='public', ignoreErrors=False):
+        """ Returns a mapping tablename -> [list of referencing table(names)].
+            ATT: this method is specific to Postgres databases!
+            ATT: This method is limited to a particular schema.
+        """
+
+        if not hasattr(_cache, 'ref_mapping'):
+            
+            d = {}
+            db = self._engine
+            rs = db.execute("select * from pg_tables where schemaname = '%s'" % schema)
+
+            for row in rs: 
+                tablename = row.tablename
+
+                try:
+                    table = sqlalchemy.Table(tablename, db, autoload=True)
+                except KeyError:
+                    if ignoreErrors:
+                        print >>sys.stderr, 'Can\'t load table %s' % tablename
+                        continue
+                    else: 
+                        raise
+                    
+                for c in table.c:
+                    fk = c.foreign_key
+                    if fk is not None:
+
+                        ref_by_table = fk.column.table
+                        ref_by_table_name = ref_by_table.name
+
+                        if not d.has_key(ref_by_table_name):
+                            d[ref_by_table_name] = list()
+
+                        if not tablename in d[ref_by_table_name]:
+                            d[ref_by_table_name].append(tablename)
+
+            _cache.ref_mapping = d
+
+        return _cache.ref_mapping
+
+
+class ZopePostgresWrapper(PythonPostgresWrapper, ZopeMixin):
+    """ A wrapper to be used from within Zope. It connects
+        the session with the transaction management of Zope.
+    """
+

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/test.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/test.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/test.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,19 @@
+from haufe.sqlalchemy import createSQLAlchemyWrapper, Model
+
+class Format(object):
+    pass
+
+m = Model({'name' : 'format', 'autodetect_relations' : True, 'mapper_class' : Format},
+          {'name' : 'medium', 'autodetect_relations' : True})
+
+
+
+w = createSQLAlchemyWrapper('postgres://postgres:postgres@cmsdb/MedienDB', model=m)
+
+print w
+f = w.getMapper('format')
+m = w.getMapper('medium')
+
+session = w.session
+for row in session.query(f).select():
+    print row.versionfiles

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/__init__.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/__init__.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/__init__.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,9 @@
+##########################################################################
+# z3c.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Freiburg, Germany
+# (C) 2007, ZOPYX Ltd. & Co. KG, Tuebingen, Germany
+# 
+# Published under der Zope Public License V 2.1 
+##########################################################################
+

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/framework.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/framework.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/framework.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# ZopeTestCase 
+#
+# COPY THIS FILE TO YOUR 'tests' DIRECTORY.
+#
+# This version of framework.py will use the SOFTWARE_HOME
+# environment variable to locate Zope and the Testing package.
+#
+# If the tests are run in an INSTANCE_HOME installation of Zope,
+# Products.__path__ and sys.path with be adjusted to include the
+# instance's Products and lib/python directories respectively.
+#
+# If you explicitly set INSTANCE_HOME prior to running the tests,
+# auto-detection is disabled and the specified path will be used 
+# instead.
+#
+# If the 'tests' directory contains a custom_zodb.py file, INSTANCE_HOME
+# will be adjusted to use it.
+#
+# If you set the ZEO_INSTANCE_HOME environment variable a ZEO setup 
+# is assumed, and you can attach to a running ZEO server (via the 
+# instance's custom_zodb.py).
+#
+##############################################################################
+#
+# The following code should be at the top of every test module:
+#
+# import os, sys
+# if __name__ == '__main__':
+#     execfile(os.path.join(sys.path[0], 'framework.py'))
+#
+# ...and the following at the bottom:
+#
+# if __name__ == '__main__':
+#     framework()
+#
+##############################################################################
+
+__version__ = '0.2.3'
+
+# Save start state
+#
+__SOFTWARE_HOME = os.environ.get('SOFTWARE_HOME', '')
+__INSTANCE_HOME = os.environ.get('INSTANCE_HOME', '')
+
+if __SOFTWARE_HOME.endswith(os.sep):
+    __SOFTWARE_HOME = os.path.dirname(__SOFTWARE_HOME)
+
+if __INSTANCE_HOME.endswith(os.sep):
+    __INSTANCE_HOME = os.path.dirname(__INSTANCE_HOME)
+
+# Find and import the Testing package
+#
+if not sys.modules.has_key('Testing'):
+    p0 = sys.path[0]
+    if p0 and __name__ == '__main__':
+        os.chdir(p0)
+        p0 = ''
+    s = __SOFTWARE_HOME
+    p = d = s and s or os.getcwd()
+    while d:
+        if os.path.isdir(os.path.join(p, 'Testing')):
+            zope_home = os.path.dirname(os.path.dirname(p))
+            sys.path[:1] = [p0, p, zope_home]
+            break
+        p, d = s and ('','') or os.path.split(p)
+    else:
+        print 'Unable to locate Testing package.',
+        print 'You might need to set SOFTWARE_HOME.'
+        sys.exit(1)
+
+import Testing 
+execfile(os.path.join(os.path.dirname(Testing.__file__), 'common.py'))
+
+# Include ZopeTestCase support
+#
+if 1:   # Create a new scope
+
+    p = os.path.join(os.path.dirname(Testing.__file__), 'ZopeTestCase')
+
+    if not os.path.isdir(p):
+        print 'Unable to locate ZopeTestCase package.',
+        print 'You might need to install ZopeTestCase.'
+        sys.exit(1)
+
+    ztc_common = 'ztc_common.py'
+    ztc_common_global = os.path.join(p, ztc_common) 
+
+    f = 0
+    if os.path.exists(ztc_common_global):
+        execfile(ztc_common_global)
+        f = 1
+    if os.path.exists(ztc_common):
+        execfile(ztc_common)
+        f = 1
+
+    if not f:
+        print 'Unable to locate %s.' % ztc_common
+        sys.exit(1)
+
+# Debug
+#
+print 'SOFTWARE_HOME: %s' % os.environ.get('SOFTWARE_HOME', 'Not set')
+print 'INSTANCE_HOME: %s' % os.environ.get('INSTANCE_HOME', 'Not set')
+sys.stdout.flush()
+

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/testSQLAlchemy.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/testSQLAlchemy.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/testSQLAlchemy.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,123 @@
+# -*- coding: iso-8859-15 -*-
+
+##########################################################################
+# z3c.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Freiburg, Germany
+# (C) 2007, ZOPYX Ltd. & Co. KG, Tuebingen, Germany
+# 
+# Published under der Zope Public License V 2.1 
+##########################################################################
+
+
+"""
+Tests, tests, tests.........
+"""
+
+
+import os
+import sys
+import unittest
+
+import sqlalchemy
+
+from zope.interface.verify import verifyClass
+from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper
+from z3c.sqlalchemy.postgres import PythonPostgresWrapper,  ZopePostgresWrapper
+from z3c.sqlalchemy.base import BaseWrapper
+from z3c.sqlalchemy import createSQLAlchemyWrapper, Model
+
+if __name__ == '__main__':
+    execfile(os.path.join(sys.path[0], 'framework.py'))
+
+
+class WrapperTests(unittest.TestCase):
+
+
+    def setUp(self):
+        from pysqlite2 import dbapi2 as sqlite
+
+        db = sqlite.connect('test')
+        cur = db.cursor()
+        try:
+            cur.execute("""DROP TABLE user""")
+        except:
+            pass
+
+        cur.execute("""CREATE TABLE user(id int4 primary key,"""
+                    """                  firstname varchar(255),"""
+                    """                  lasttname varchar(255)"""
+                    """)""")
+
+        try:
+            cur.execute("""DROP TABLE skills""")
+        except:
+            pass
+        cur.execute("""CREATE TABLE skills(id int4 primary key,"""
+                    """                    user_id int4, """
+                    """                    name varchar(255),"""
+                    """                    FOREIGN KEY (user_id) REFERENCES xxxx"""
+                    """)""")
+        db.close()
+
+    def testIFaceBaseWrapper (self):
+        verifyClass(ISQLAlchemyWrapper , BaseWrapper)
+
+    def testIFacePythonPostgres(self):
+        verifyClass(ISQLAlchemyWrapper , PythonPostgresWrapper)
+
+    def testIFaceZopePostgres(self):
+        verifyClass(ISQLAlchemyWrapper , ZopePostgresWrapper)
+
+    def testSimplePopulation(self):
+        db = createSQLAlchemyWrapper('sqlite:///test')
+        # obtain mapper for table 'user'
+
+        User = db.getMapper('user')
+        session = db.session
+
+        rows = session.query(User).select()
+        self.assertEqual(len(rows), 0)
+
+        session.save(User(id=1, firstname='udo', lastname='juergens'))
+        session.save(User(id=2, firstname='heino', lastname='n/a'))
+        session.flush()
+        
+        rows = session.query(User).select()
+        self.assertEqual(len(rows), 2)
+
+    def testMapperWithCustomModel(self):
+
+        class myUser(object): 
+            pass
+
+        M = Model()
+        M.add('user', mapper_class=myUser)
+
+        db = createSQLAlchemyWrapper('sqlite:///test', model=M)
+        User = db.getMapper('user')
+        self.assertEqual(User, myUser)
+
+
+    def testModelWeirdParameters(self):
+        M = Model()
+        self.assertRaises(ValueError, M.add, 'user', relations=('foo', 'bar'), autodetect_relations=True)
+
+    def testModelNonExistingTables(self):
+        M = Model()
+        M.add('non_existing_table')
+        db = createSQLAlchemyWrapper('sqlite:///test', model=M)
+        try:
+            foo = db.getMapper('nonn_existing_table')
+        except sqlalchemy.exceptions.NoSuchTableError:
+            pass
+
+
+def test_suite():
+    from unittest import TestSuite, makeSuite
+    suite = TestSuite()
+    suite.addTest(makeSuite(WrapperTests))
+    return suite
+
+if __name__ == '__main__':
+    framework()

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/util.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/util.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/util.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,71 @@
+##########################################################################
+# haufe.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Author: Andreas Jung
+##########################################################################
+
+""" 
+Some helper methods
+"""
+
+
+from sqlalchemy.engine.url import make_url
+
+from zope.component import getService, getGlobalServices, getUtilitiesFor 
+from zope.component.utility import GlobalUtilityService
+from zope.component.interfaces import IUtilityService
+from zope.component.servicenames import Utilities 
+
+from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper
+from z3c.sqlalchemy.postgres import ZopePostgresWrapper, PythonPostgresWrapper 
+from z3c.sqlalchemy.base import BaseWrapper
+
+__all__ = ('createSQLAlchemyWrapper', 'registerSQLAlchemyWrapper', 'allRegisteredSQLAlchemyWrappers')
+
+
+def createSQLAlchemyWrapper(dsn, model=None, echo=False, forZope=False):
+    """ Convenience method to generate a wrapper for a DSN and a model.
+        This method hides all database related magic from the user. 
+        Set 'forZope' to True for a Zope related wrapper.
+    """
+
+    url = make_url(dsn)
+    driver = url.drivername
+
+    klass = BaseWrapper
+
+    if driver == 'postgres':
+        klass = forZope and ZopePostgresWrapper or PythonPostgresWrapper
+
+    return klass(dsn, echo=echo, model=model)
+
+
+def registerSQLAlchemyWrapper(wrapper, name):
+    """ register a SQLAlchemyWrapper as named utility """
+
+    # Bootstrap utility service
+    sm = getGlobalServices()
+    sm.defineService(Utilities, IUtilityService)
+    sm.provideService(Utilities, GlobalUtilityService())
+
+    # register wrapper 
+    utilityService = getService(Utilities)
+    utilityService.provideUtility(ISQLAlchemyWrapper, wrapper, name)
+
+
+def allRegisteredSQLAlchemyWrappers():
+    """ return a dict containing information for all
+        registered wrappers.
+    """
+
+    d = list()
+    for name, wrapper in getUtilitiesFor(ISQLAlchemyWrapper):
+        d.append({'name' : name,
+                  'dsn' : wrapper.dsn,
+                  'echo' : wrapper.echo,
+                })
+    return d
+
+
+if __name__ == '__main__':
+    print createWrapper('postgres://test:test@db.example.com/TestDB', None)

Added: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/zope_mixin.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/zope_mixin.py	2007-03-17 07:37:09 UTC (rev 73268)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/zope_mixin.py	2007-03-17 08:13:33 UTC (rev 73269)
@@ -0,0 +1,92 @@
+##########################################################################
+# haufe.sqlalchemy - A SQLAlchemy wrapper for Python/Zope
+#
+# (C) 2007, Haufe-Mediengruppe, Author: Andreas Jung
+##########################################################################
+
+import sys
+import threading
+
+import sqlalchemy
+
+from zope.interface import implements
+
+from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper
+from z3c.sqlalchemy.base import BaseWrapper
+
+import transaction
+from transaction.interfaces import IDataManager
+
+_cache = threading.local() # module-level cache 
+
+marker = object
+
+
+class DataManager(object):
+    """ Wraps session into transaction context of Zope """
+
+    implements(IDataManager)
+
+    def __init__(self, session):
+        self.session = session
+
+    def abort(self, trans):
+        pass
+
+    def tpc_begin(self, trans):
+        pass
+
+    def commit(self, trans):
+        self.session.flush()
+
+    def tpc_vote(self, trans):
+        pass
+
+    def tpc_finish(self, trans):
+        pass
+
+    def tpc_abort(self, trans):
+        pass
+
+    def sortKey(self):
+        return str(id(self))
+
+
+
+class ZopeMixin:
+    """ A wrapper to be used from within Zope. It connects
+        the session with the transaction management of Zope.
+    """
+
+    @property
+    def session(self):
+
+        if not hasattr(_cache, 'last_transaction'):
+            _cache.last_transaction = None
+            _cache.last_session = None
+
+        # get current transaction
+        txn = transaction.get()
+        txn_str = str(txn)
+
+        # return cached session if we are within the same transaction
+        # and same thread
+        if txn_str == _cache.last_transaction:
+            return _cache.last_session
+
+        # no cached session, let's create a new one
+        session = sqlalchemy.create_session(self._engine)
+                                          
+        # register a DataManager with the current transaction
+        DM = DataManager(session)
+        txn.join(DM)
+
+        # update thread-local cache
+        _cache.last_transaction = txn_str
+        _cache.last_session = session
+
+        # return the session
+        return session 
+
+class ZopeBaseWrapper(BaseWrapper, ZopeMixin):
+    """ A generic wrapper for Zope """



More information about the Checkins mailing list