[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