[Checkins] SVN: z3c.zalchemy/ reserved namespace z3c.zalchemy, started sandbox and copy zalchemy from svn.kartnaller.at here

Bernd Dorn bernd.dorn at fhv.at
Fri Apr 21 04:24:01 EDT 2006


Log message for revision 67198:
  reserved namespace z3c.zalchemy, started sandbox and copy zalchemy from svn.kartnaller.at here
  
  

Changed:
  A   z3c.zalchemy/
  A   z3c.zalchemy/sandbox/
  A   z3c.zalchemy/sandbox/src/
  A   z3c.zalchemy/sandbox/src/z3c/
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/README.txt
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/TODO.txt
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/__init__.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/browser/
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/browser/__init__.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/browser/configure.zcml
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/configure.zcml
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/container.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/datamanager.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/interfaces.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/meta.zcml
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/metaconfigure.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/metadirectives.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/TRANSACTION.txt
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/__init__.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/environ.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/test_directives.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/test_zalchemy.py
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/zalchemy-configure.zcml
  A   z3c.zalchemy/sandbox/src/z3c/zalchemy/zalchemy-meta.zcml

-=-
Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/README.txt
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/README.txt	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/README.txt	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,211 @@
+=====================
+SQLAlchemy and Zope 3
+=====================
+
+"zalchemy" integrates the object relational mapper sqlalchemy into zope 3
+as SQLOS integrates sqlobject.
+
+zalchemy tries to do it's best not to interfere with the standard sqlalchemy
+usage.
+The main part of zalchemy is the integration of the sqlalchemy transaction
+into the zope transaction.
+This is solved by using a data manager which joins the zope transaction for
+every newly created thread.
+
+
+zalchemy class implementation
+=============================
+
+There is no difference between the usage of sqlalchemy together with zope.
+
+zalchemy provides a transparent way to connect a table to a database (engine).
+
+A SQLAlchemy engine is represented as a utility :
+
+  >>> import os
+  >>> from zalchemy.datamanager import AlchemyEngineUtility
+  >>> engineUtil = AlchemyEngineUtility(
+  ...     'sqlite',
+  ...     dns='sqlite://',
+  ...     )
+
+We create our tables as usual sqlalchemy table :
+Note that we use a ProxyEngine which is important here.
+The real connection to database engine will be done later in our utility.
+
+  >>> import sqlalchemy
+  >>> engine = sqlalchemy.ext.proxy.ProxyEngine()
+
+  >>> aTable = sqlalchemy.Table(
+  ...     'aTable',
+  ...     engine,
+  ...     sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True),
+  ...     sqlalchemy.Column('value', sqlalchemy.Integer),
+  ...     )
+
+Define a simple class which will be used later to map to a database table.
+
+  >>> class A(object):
+  ...     pass
+
+Now we map the table to our class.
+
+  >>> sqlalchemy.assign_mapper(A, aTable)
+
+The next step is to connect the table to the database engine.
+We use our utility for that :
+
+  >>> engineUtil.addTable(aTable)
+
+To let zalchemy do his magic thing we need to register our database utility
+as a named utility :
+
+  >>> from zope.component import provideUtility
+  >>> provideUtility(engineUtil, name='sqlite')
+
+From now on zalchemy takes care of the zope transaction process behind the
+scenes :
+- connect the engine to the tables for every thread
+- create the tables in the database if not already done
+- handle the two phase commit process
+- disconnect the engine from the tables at the end of the thread
+
+Now let's try to use our database objects :
+
+  >>> a = A()
+  >>> a.value = 1
+  >>> sqlalchemy.objectstore.commit()
+  Traceback (most recent call last):
+  ...
+  AttributeError: No connection established
+
+Ups, this is because the tabel is not connected to a real database engine.
+We need to use zope transactions.
+
+First let's clear the current unit of work :
+
+  >>> sqlalchemy.objectstore.clear()
+
+Note that the transaction handling is done inside zope.
+
+  >>> import transaction
+  >>> txn = transaction.begin()
+
+Then we need to simulate a beforeTraversal Event :
+
+  >>> from zalchemy.datamanager import beforeTraversal
+  >>> beforeTraversal(None)
+
+  >>> a = A()
+  >>> a.value = 123
+
+  >>> transaction.get().commit()
+
+Now let's try to get the object back in a new transaction :
+
+  >>> txn = transaction.begin()
+  >>> beforeTraversal(None)
+
+  >>> a = A.get(1)
+  >>> a.value
+  123
+
+  >>> transaction.get().commit()
+
+
+Multiple databases
+------------------
+
+  >>> engine2Util = AlchemyEngineUtility(
+  ...     'sqlite2',
+  ...     dns='sqlite://',
+  ...     )
+
+  >>> engine = sqlalchemy.ext.proxy.ProxyEngine()
+  >>> provideUtility(engine2Util, name='sqlite2')
+
+  >>> bTable = sqlalchemy.Table(
+  ...     'bTable',
+  ...     engine,
+  ...     sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True),
+  ...     sqlalchemy.Column('value', sqlalchemy.String),
+  ...     )
+  >>> class B(object):
+  ...     pass
+  >>> sqlalchemy.assign_mapper(B, bTable)
+  >>> engine2Util.addTable(bTable)
+
+  >>> txn = transaction.begin()
+  >>> beforeTraversal(None)
+
+  >>> b = B()
+  >>> b.value = 'b1'
+  >>> B.get(1)
+
+  >>> a = A()
+  >>> a.value = 321
+
+  >>> transaction.get().commit()
+
+  >>> txn = transaction.begin()
+  >>> beforeTraversal(None)
+
+  >>> a = A.get(1)
+  >>> b = B.get(1)
+  >>> b.value
+  u'b1'
+
+  >>> transaction.get().commit()
+
+Glitches
+--------
+
+  >>> txn = transaction.begin()
+  >>> beforeTraversal(None)
+
+  >>> startLen = A.mapper.count()
+
+  >>> a1 = A()
+  >>> a1.value = 123
+
+  >>> A.mapper.count() == startLen + 1
+  False
+
+At this time a1 is not stored in the database. It is only stored when doing an
+objectstore.commit().
+At this time the object also has no id.
+
+  >>> a1.id is None
+  True
+
+An explicit commit for a1 solves the problem :
+
+  >>> sqlalchemy.objectstore.commit(a1)
+
+  >>> A.mapper.count() == startLen + 1
+  True
+  >>> a1.id is None
+  False
+
+  >>> transaction.get().commit()
+
+
+Exceptions
+----------
+
+A table must use a ProxyEngine.
+
+  >>> illegalEngine = sqlalchemy.create_engine("sqlite://")
+
+  >>> illegalTable = sqlalchemy.Table(
+  ...     'illegalTable',
+  ...     illegalEngine,
+  ...     sqlalchemy.Column('id', sqlalchemy.Integer, primary_key=True),
+  ...     sqlalchemy.Column('value', sqlalchemy.Integer),
+  ...     )
+
+  >>> engineUtil.addTable(illegalTable) #doctest: +ELLIPSIS
+  Traceback (most recent call last):
+  ...
+  TypeError: ...
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/README.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/TODO.txt
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/TODO.txt	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/TODO.txt	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,25 @@
+- test rollback behaviour
+
+- better README !!!
+   Show the problems with newely created objects.
+
+- data manager
+   It is not fully clear what should be done in commit and what should be done
+   in tpc_vote !
+
+- where to create a table for a fresh database
+   Remove the hack in datamanager.beforeTraversal
+
+- container implementation is some kind of a quick hack
+
+- sqlalchemy's proxy handling is not using a global connection pool !
+   Each proxy creates a new engine even if it is the same database.
+
+       Solved by using the database utility to create the engine and directly
+       assign this engine to all proxies.
+       Shotcuts ProxyEngines's connect !?
+
+- use of ActiveMapper
+
+- Adapter for sqlalchemy mapped object deletion
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/TODO.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/__init__.py
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/__init__.py	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/__init__.py	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1 @@
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/browser/__init__.py
===================================================================


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/browser/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/browser/configure.zcml
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/browser/configure.zcml	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/browser/configure.zcml	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,31 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:browser="http://namespaces.zope.org/browser"
+           i18n_domain="zalchemy"
+           >
+
+  <browser:addMenuItem
+      class="zalchemy.container.SQLAlchemyContainer"
+      title="SQLAlchemy Container"
+      description="A persistent container for mapped SQLAlchemy instances"
+      permission="zope.ManageContent"
+      view="addSQLAlchemyContainer.html"
+      />
+
+  <browser:addform
+      for="*"
+      name="addSQLAlchemyContainer.html"
+      content_factory="zalchemy.container.SQLAlchemyContainer"
+      schema="zalchemy.interfaces.ISQLAlchemyContainer"
+      set_before_add="className"
+      permission="zope.ManageContent"
+      />
+
+  <browser:containerViews
+      for="zalchemy.interfaces.ISQLAlchemyContainer"
+      index="zope.View"
+      contents="zope.View"
+      add="zope.ManageContent"
+      />
+
+</configure>
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/browser/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/configure.zcml
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/configure.zcml	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/configure.zcml	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,42 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:browser="http://namespaces.zope.org/browser"
+           xmlns:alchemy="http://namespaces.zalchemy.org/alchemy"
+           i18n_domain="zalchemy"
+           >
+
+  <class class=".container.SQLAlchemyContainer">
+
+    <factory
+        id="zalchemy.container.SQLAlchemyContainer"
+        title="SQLAlchemy Container"
+        description="A container for mapped sqlalchemy instances" />
+
+    <require
+        permission="zope.View"
+        interface="zope.app.container.interfaces.IReadContainer"
+        />
+
+    <require
+        permission="zope.ManageContent"
+        interface="zope.app.container.interfaces.IWriteContainer"
+        />
+
+  </class>
+
+  <adapter
+      provides="zope.app.container.interfaces.INameChooser"
+      for="zalchemy.interfaces.ISQLAlchemyContainer"
+      permission="zope.Public"
+      factory="zalchemy.container.SQLAlchemyNameChooser"
+      />
+
+  <subscriber
+     handler="zalchemy.datamanager.beforeTraversal"
+     for="zope.app.publication.interfaces.IBeforeTraverseEvent"
+     trusted="True"
+     />
+
+  <include package=".browser" />
+
+</configure>
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/container.py
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/container.py	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/container.py	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,157 @@
+##############################################################################
+#
+# Copyright (c) 2006 ROBOTECH Logistiksysteme GmbH
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+from persistent import Persistent
+
+from zope.security.proxy import removeSecurityProxy
+
+from zope.app.container.contained import Contained
+from zope.app.container.contained import ContainedProxy
+from zope.app.container.contained import NameChooser
+from zope.app.container.interfaces import IContained
+from zope.app.location.interfaces import ILocation
+from zope.app.exception.interfaces import UserError
+
+from zope import interface
+from zope.configuration.name import resolve
+
+import sqlalchemy
+
+from interfaces import ISQLAlchemyContainer
+
+
+def contained(obj, parent=None, name=None):
+    """An implementation of zope.app.container.contained.contained
+    that doesn't generate events, for internal use.
+
+    Borrowed from SQLOS.
+    """
+    if (parent is None):
+        raise TypeError, 'Must provide a parent'
+
+    if not IContained.providedBy(obj):
+        if ILocation.providedBy(obj):
+            directlyProvides(obj, IContained, directlyProvidedBy(obj))
+        else:
+            obj = ContainedProxy(obj)
+
+    oldparent = obj.__parent__
+    oldname = obj.__name__
+
+    if (oldparent is None) or not (oldparent is parent
+                                   or sameProxiedObjects(oldparent, parent)):
+        obj.__parent__ = parent
+
+    if oldname != name and name is not None:
+        obj.__name__ = name
+
+    return obj
+
+
+class SQLAlchemyNameChooser(NameChooser):
+
+    def checkName(self, name, container):
+
+        if isinstance(name, str):
+            name = unicode(name)
+        elif not isinstance(name, unicode):
+            raise TypeError("Invalid name type", type(name))
+
+        unproxied = removeSecurityProxy(container)
+        if not name.startswith(unproxied._class.__name__+'.'):
+            raise UserError(
+                _("Invalid name for SQLAlchemy object")
+                )
+        try:
+            id = int(name.split('.')[-1])
+        except:
+            raise UserError(
+                _("Invalid id for SQLAlchemy object")
+                )
+
+        return True
+
+    def chooseName(self, name, obj):
+        # commit the object to make sure it contains an id
+        sqlalchemy.objectstore.commit(obj)
+        return '%s.%i'%(obj.__class__.__name__, obj.id)
+
+
+class SQLAlchemyContainer(Persistent, Contained):
+    interface.implements(ISQLAlchemyContainer)
+
+    _className = ''
+    _class = None
+
+    def setClassName(self, name):
+        self._className = name
+        self._class=resolve(name)
+
+    def getClassName(self):
+        return self._className
+    className = property(getClassName, setClassName)
+
+    def keys(self):
+        for name, obj in self.items():
+            yield name
+
+    def values(self):
+        for name, obj in self.items():
+            yield obj
+
+    def __iter__(self):
+        return iter(self.keys())
+
+    def items(self):
+        for obj in self._class.mapper.select():
+            name = '%s.%i'%(self._class.__name__, obj.id)
+            yield (name, contained(obj, self, name) )
+
+    def __getitem__(self, name):
+        if not isinstance(name, basestring):
+            raise KeyError, "%s is not a string" % name
+        vals = name.split('.')
+        try:
+            id=int(vals[-1])
+        except ValueError:
+            return None
+        obj = self._class.mapper.selectfirst(self._class.c.id==id)
+        if obj is None:
+            raise KeyError, name
+        return contained(obj, self, name)
+
+    def get(self, name, default = None):
+        try:
+            return self[name]
+        except KeyError:
+            return default
+    
+    def __contains__(self, name):
+        return self.get(name) is not None
+
+    def __len__(self):
+        try:
+            return self._class.mapper.count()
+        except sqlalchemy.exceptions.SQLError:
+            # we don't want an exception in case of database problems
+            return 0
+
+    def __delitem__(self, name):
+        obj = self[name]
+        #TODO: better delete objects using a delete adapter
+        #      to dependency handling.
+        obj.delete()
+
+    def __setitem__(self, name, item):
+        sqlalchemy.objectstore.commit(item)
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/container.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/datamanager.py
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/datamanager.py	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/datamanager.py	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,137 @@
+##############################################################################
+#
+# Copyright (c) 2006 ROBOTECH Logistiksysteme GmbH
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+import thread
+from threading import local
+from ZODB.utils import WeakSet
+
+from zope.interface import implements
+from zope.component import getUtilitiesFor
+
+from transaction import get, manager
+from transaction.interfaces import IDataManager, ISynchronizer
+
+from interfaces import IAlchemyEngineUtility
+
+from sqlalchemy import objectstore, create_engine
+import sqlalchemy
+
+
+class AlchemyEngineUtility(object):
+    """A utility providing the dns for alchemy database engines.
+    """
+    implements(IAlchemyEngineUtility)
+
+    def __init__(self, name, dns, echo=False, **kwargs):
+        self.name = name
+        self.dns = dns
+        self.echo = echo
+        self.kw=kwargs
+        self.tables = []
+        self.storage = local()
+        self.createdTables = {}
+
+    def addTable(self, table):
+        if not isinstance(table.engine, sqlalchemy.ext.proxy.ProxyEngine):
+            raise TypeError(table.engine)
+        utils = getUtilitiesFor(IAlchemyEngineUtility)
+        for name, util in utils:
+            if table in util.tables:
+                #TODO: should raise an exception here
+                return
+        self.tables.append(table)
+
+    def connectTablesForThread(self):
+        # create a thread local engine
+        engine=getattr(self.storage,'engine',None)
+        if engine is not None:
+            return
+        self.storage.engine = create_engine(self.dns,
+                                            self.kw,
+                                            echo=self.echo)
+        engine = self.storage.engine
+        # create a data manager
+        if self.echo:
+            engine.log('adding data manager for %s'%self.name)
+        self.storage.dataManager = AlchemyDataManager(self)
+        txn = manager.get()
+        txn.join(self.storage.dataManager)
+        # connect the tables to the engine
+        for table in self.tables:
+            table.engine.engine = engine
+            #TODO: this is a bad hack which must go away soon !
+            if table not in self.createdTables:
+                try:
+                    # make sure the table exists :
+                    table.create()
+                except:
+                    pass
+                self.createdTables[table]=True
+
+    def dataManagerFinished(self):
+        self.storage.engine=None
+        # disconnect the tables from the engine
+        for table in self.tables:
+            table.engine.engine = None
+
+    def getEngine(self):
+        return getattr(self.storage,'engine',None)
+    engine = property(getEngine)
+
+
+class AlchemyDataManager(object):
+    """Takes care of the transaction process in zope.
+    """
+    implements(IDataManager)
+
+    vote = False
+
+    def __init__(self, util):
+        self.util = util
+        self.engine = util.getEngine()
+        self.engine.begin()
+
+    def abort(self, trans):
+        self.engine.rollback()
+        objectstore.clear()
+        self.util.dataManagerFinished()
+
+    def tpc_begin(self, trans):
+        pass
+
+    def commit(self, trans):
+        objectstore.commit()
+
+    def tpc_vote(self, trans):
+        pass
+
+    def tpc_finish(self, trans):
+        self.engine.commit()
+        if self.util.echo:
+            self.engine.log('commit for %s'%self.util.name)
+        objectstore.clear()
+        self.util.dataManagerFinished()
+
+    def tpc_abort(self, trans):
+        self.engine.rollback()
+        objectstore.clear()
+        self.util.dataManagerFinished()
+
+    def sortKey(self):
+        return str(id(self))
+
+def beforeTraversal(event):
+    utils = getUtilitiesFor(IAlchemyEngineUtility)
+    for name, util in utils:
+        util.connectTablesForThread()
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/datamanager.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/interfaces.py
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/interfaces.py	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/interfaces.py	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2006 ROBOTECH Logistiksysteme GmbH
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+from zope import interface
+from zope import schema
+from zope.app.container.interfaces import IContainer
+from zope.app.container.constraints import contains, containers
+
+
+class ISQLAlchemyObject(interface.Interface):
+    """Marker interface for mapped sqlalchemy objects.
+    """
+
+
+class ISQLAlchemyContainer(IContainer):
+    """A zope container containing sqlalchemy objects.
+    """
+    className = schema.TextLine(
+            title = u'Class',
+            required = True,
+            )
+    contains(ISQLAlchemyObject)
+
+
+class ISQLAlchemyObjectContained(interface.Interface):
+    """Limit containment to SQLAlchemy containers
+    """
+    containers(ISQLAlchemyContainer)
+
+
+class IAlchemyEngineUtility(interface.Interface):
+    dns = schema.Text(
+            title = u'DNS',
+            required = True,
+            )
+    echo = schema.Bool(
+            title=u'echo sql',
+            required=False,
+            default=False
+            )
+
+    def addTable():
+        """
+        """
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/interfaces.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/meta.zcml
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/meta.zcml	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/meta.zcml	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,22 @@
+<configure xmlns='http://namespaces.zope.org/zope'
+           xmlns:meta="http://namespaces.zope.org/meta"
+           i18n_domain="zalchemy">
+
+  <meta:directives namespace="http://namespaces.zalchemy.org/alchemy">
+
+    <meta:directive
+        name="engine"
+        schema=".metadirectives.IEngineDirective"
+        handler=".metaconfigure.engine"
+        />
+
+    <meta:directive
+        name="connect"
+        schema=".metadirectives.IConnectDirective"
+        handler=".metaconfigure.connect"
+        />
+
+  </meta:directives>
+
+</configure>
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/meta.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/metaconfigure.py
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/metaconfigure.py	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/metaconfigure.py	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,39 @@
+##############################################################################
+#
+# Copyright (c) 2006 ROBOTECH Logistiksysteme GmbH
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+from zope import component
+from zope.app.component.metaconfigure import utility, PublicPermission
+
+from datamanager import AlchemyEngineUtility
+from interfaces import IAlchemyEngineUtility
+
+def engine(_context, name, dns, echo=False, **kwargs):
+    component = AlchemyEngineUtility(name, dns, echo, **kwargs)
+    utility(_context,
+            IAlchemyEngineUtility,
+            component,
+            permission=PublicPermission,
+            name=name)
+
+def connect(_context, engine, table):
+    _context.action(
+            discriminator = (engine, table),
+            callable = connector,
+            args = (engine, table)
+            )
+
+def connector(engine, table):
+    util = component.getUtility(IAlchemyEngineUtility, engine)
+    util.addTable(table)
+    #connections.append((util, table))
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/metaconfigure.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/metadirectives.py
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/metadirectives.py	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/metadirectives.py	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,54 @@
+##############################################################################
+#
+# Copyright (c) 2006 ROBOTECH Logistiksysteme GmbH
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+from zope import interface
+from zope import schema
+from zope.configuration.fields import GlobalObject
+
+
+class IEngineDirective(interface.Interface):
+    """Define an engine.
+    """
+    name = schema.Text(
+            title = u'Engine name',
+            required = True,
+            )
+    dns = schema.Text(
+            title = u'DNS for the database connection',
+            required = True,
+            )
+    echo = schema.Bool(
+            title = u'Echo SQL statement',
+            required = False,
+            default=False
+            )
+
+# Arbitrary keys and values are allowed to be passed to the viewlet.
+IEngineDirective.setTaggedValue('keyword_arguments', True)
+
+
+class IConnectDirective(interface.Interface):
+    """
+    """
+    engine = schema.Text(
+            title = u'Engine',
+            description = u'The name of the engine to connect a table to',
+            required = True,
+            )
+    table = GlobalObject(
+            title = u'Table',
+            description = u'The table to ceonnect the engine to.'\
+                          u'The Table must contain a ProxyEngine !',
+            required = True,
+            )
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/metadirectives.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/TRANSACTION.txt
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/TRANSACTION.txt	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/TRANSACTION.txt	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,180 @@
+Zope transactions and sqlalchemy
+================================
+
+When a zope transaction is used also a sqlalchemy transaction must be
+activated. "zalchemy" installs a data manager every time a new zope
+transaction is created. 
+
+Setup zope's test environment :
+
+  >>> from zope.app.testing.placelesssetup import setUp, tearDown
+  >>> setUp()
+
+Create a utility to provide a database :
+
+  >>> from zope.component import provideUtility
+  >>> from zalchemy.datamanager import AlchemyEngineUtility
+  >>> engineUtility = AlchemyEngineUtility('database',
+  ...                                      'sqlite://filename=abc.db',
+  ...                                      echo=False)
+  >>> provideUtility(engineUtility, name="test")
+
+Setup a sqlalchemy table and class :
+
+  >>> import sqlalchemy
+  >>> proxy = sqlalchemy.ext.proxy.ProxyEngine()
+  >>> aTable = sqlalchemy.Table(
+  ...     'aTable',
+  ...     proxy,
+  ...     sqlalchemy.Column('id', sqlalchemy.Integer, primary_key = True),
+  ...     sqlalchemy.Column('x', sqlalchemy.Integer),
+  ...     )
+  >>> class A(object):
+  ...   pass
+  >>> sqlalchemy.assign_mapper(A, aTable)
+
+
+We need to register a connection between the engine and the table.
+This connetion will be used later to connect the proxy in aTable with
+a real engine.
+
+  >>> import os
+  >>> try:
+  ...     os.remove('abc.db')
+  ... except:
+  ...     pass
+
+We need a connection between the database and the table :
+  This is done manually here but can also be done using the
+  connect ZCML directive.
+
+  >>> engineUtility.addTable(aTable)
+
+Now start a zope transaction :
+
+  >>> import transaction
+  >>> txn = transaction.begin()
+
+Now we need to connect our table to a real engine :
+
+  >>> from zalchemy.datamanager import beforeTraversal
+  >>> beforeTraversal(None)
+
+  >>> aTable.engine.engine is not None
+  True
+
+Because we now have a connection to a brand new database we need to create
+the table.
+
+  >>> try:
+  ...     dummy = aTable.create()
+  ... except:
+  ...    pass
+
+  >>> a=A()
+  >>> a.x = 1
+
+  >>> transaction.get().commit()
+  >>> a=None
+
+Now we must be able to use the newly created object from within another
+thread :
+
+  >>> import threading
+  >>> log = []
+  >>> def do():
+  ...     txn = transaction.begin()
+  ...     beforeTraversal(None)
+  ...     obj = A.get(1)
+  ...     log.append(obj.x)
+  ...     obj.x+= 1
+  ...     transaction.get().commit()
+  ...
+
+  >>> thread = threading.Thread(target=do)
+  >>> thread.start()
+  >>> thread.join()
+  >>> log
+  [1]
+  >>> log = []
+  >>> thread = threading.Thread(target=do)
+  >>> thread.start()
+  >>> thread.join()
+  >>> log
+  [2]
+
+  >>> def doSubThread():
+  ...     txn = transaction.begin()
+  ...     beforeTraversal(None)
+  ...     obj = A.get(1)
+  ...     thread = threading.Thread(target=do)
+  ...     thread.start()
+  ...     thread.join()
+  ...     log.append(obj.x)
+  ...     obj.x+= 1
+  ...     transaction.get().commit()
+  ...
+
+  >>> log = []
+  >>> thread = threading.Thread(target=doSubThread)
+  >>> thread.start()
+  >>> thread.join()
+
+TODO:
+Can we accept this ?
+This result shows, that a running transaction is not aborted or rolled back
+if another transaction was commited between begin and commit !
+Or does this only happen with sqlite ?
+
+  >>> log
+  [3, 3]
+
+Use of a second table with the same engine :
+
+  >>> bTable = sqlalchemy.Table(
+  ...     'bTable',
+  ...     proxy,
+  ...     sqlalchemy.Column('id', sqlalchemy.Integer, primary_key = True),
+  ...     sqlalchemy.Column('x', sqlalchemy.Integer),
+  ...     )
+  >>> class B(object):
+  ...   pass
+  >>> sqlalchemy.assign_mapper(B, bTable)
+
+Connect the new table to the already existing database :
+
+  >>> engineUtility.addTable(bTable)
+
+  >>> log = []
+  >>> def createB():
+  ...     txn = transaction.begin()
+  ...     beforeTraversal(None)
+  ...     obj=B()
+  ...     obj.x=1
+  ...     log.append(obj.x)
+  ...     transaction.get().commit()
+  ...
+  >>> def doB():
+  ...     txn = transaction.begin()
+  ...     beforeTraversal(None)
+  ...     obj = B.get(1)
+  ...     log.append(obj.x)
+  ...     obj.x+= 1
+  ...     transaction.get().commit()
+  ...
+
+  >>> thread = threading.Thread(target=createB)
+  >>> thread.start()
+  >>> thread.join()
+  >>> log
+  [1]
+
+  >>> log = []
+  >>> thread = threading.Thread(target=doB)
+  >>> thread.start()
+  >>> thread.join()
+  >>> log
+  [1]
+
+  >>> tearDown()
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/TRANSACTION.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/__init__.py
===================================================================


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/environ.py
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/environ.py	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/environ.py	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,16 @@
+import sqlalchemy
+
+engine = sqlalchemy.ext.proxy.ProxyEngine()
+
+testTable = sqlalchemy.Table(
+        'testTable',
+        engine,
+        sqlalchemy.Column('id', sqlalchemy.Integer, primary_key = True),
+        sqlalchemy.Column('x', sqlalchemy.Integer),
+        )
+
+illegalTable = sqlalchemy.Table(
+        'illegalTable',
+        sqlalchemy.create_engine('sqlite://'),
+        sqlalchemy.Column('id', sqlalchemy.Integer, primary_key = True),
+        )


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/environ.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/test_directives.py
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/test_directives.py	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/test_directives.py	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,77 @@
+import unittest
+from cStringIO import StringIO
+
+from zope import component
+
+from zope.configuration.xmlconfig import xmlconfig, XMLConfig
+from zope.testing.doctestunit import DocTestSuite
+from zope.app.testing.placelesssetup import PlacelessSetup
+
+import zalchemy
+from zalchemy.interfaces import IAlchemyEngineUtility
+
+template = """<configure
+   xmlns='http://namespaces.zope.org/zope'
+   xmlns:test='http://www.zope.org/NS/Zope3/test'
+   xmlns:alchemy='http://namespaces.zalchemy.org/alchemy'
+   i18n_domain='zope'>
+   %s
+   </configure>"""
+
+class TestDirectives(PlacelessSetup, unittest.TestCase):
+
+    def setUp(self):
+        super(TestDirectives, self).setUp()
+        XMLConfig('meta.zcml', zalchemy)()
+
+    def testEngineDirective(self):
+        xmlconfig(StringIO(template % (
+            '''
+            <alchemy:engine
+                name="sqlite"
+                dns="sqlite"
+                echo="True"
+                filename="testdatabase.db"
+                />
+            '''
+            )))
+        util = component.getUtility(IAlchemyEngineUtility,'sqlite')
+        self.assertNotEqual(util, None)
+        self.assertEqual(util.dns, 'sqlite')
+        self.assertEqual(util.echo, True)
+        self.assertEqual(util.kw['filename'], 'testdatabase.db')
+
+    def testConnectDirective(self):
+        from environ import testTable
+        self.assertRaises(AttributeError,
+                lambda : testTable.engine.engine)
+        xmlconfig(StringIO(template % (
+            '''
+            <alchemy:engine
+                name="sqlite-in-memory"
+                dns="sqlite://:memory:"
+                />
+            <alchemy:connect
+                engine="sqlite-in-memory"
+                table="zalchemy.tests.environ.testTable"
+                />
+            '''
+            )))
+        util = component.getUtility(IAlchemyEngineUtility,'sqlite-in-memory')
+        self.assert_(len(util.tables)==1)
+        self.assertEqual(util.tables[0],
+                         zalchemy.tests.environ.testTable)
+
+    def tearDown(self):
+        PlacelessSetup.tearDown(self)
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(TestDirectives),
+        #DocTestSuite(),
+        ))
+
+if __name__ == "__main__":
+    unittest.TextTestRunner().run(test_suite())
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/test_directives.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/test_zalchemy.py
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/test_zalchemy.py	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/test_zalchemy.py	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1,12 @@
+import unittest
+from zope.testing.doctestunit import DocFileSuite
+
+def test_suite():
+    return unittest.TestSuite((
+            DocFileSuite('TRANSACTION.txt'),
+            DocFileSuite('../README.txt'),
+            ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/tests/test_zalchemy.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/zalchemy-configure.zcml
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/zalchemy-configure.zcml	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/zalchemy-configure.zcml	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1 @@
+<include package="zalchemy"/>


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/zalchemy-configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Added: z3c.zalchemy/sandbox/src/z3c/zalchemy/zalchemy-meta.zcml
===================================================================
--- z3c.zalchemy/sandbox/src/z3c/zalchemy/zalchemy-meta.zcml	2006-04-21 07:58:08 UTC (rev 67197)
+++ z3c.zalchemy/sandbox/src/z3c/zalchemy/zalchemy-meta.zcml	2006-04-21 08:24:00 UTC (rev 67198)
@@ -0,0 +1 @@
+<include package="zalchemy" file="meta.zcml" />


Property changes on: z3c.zalchemy/sandbox/src/z3c/zalchemy/zalchemy-meta.zcml
___________________________________________________________________
Name: svn:eol-style
   + native



More information about the Checkins mailing list