[Checkins] SVN: z3c.sqlalchemy/trunk/ merging zope.sqlalchemy integration branch

Andreas Jung andreas at andreas-jung.com
Mon May 19 01:38:04 EDT 2008


Log message for revision 86825:
  merging zope.sqlalchemy integration branch
  

Changed:
  U   z3c.sqlalchemy/trunk/CHANGES.txt
  U   z3c.sqlalchemy/trunk/README.txt
  U   z3c.sqlalchemy/trunk/buildout.cfg
  U   z3c.sqlalchemy/trunk/setup.py
  U   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/base.py
  D   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/doc/
  U   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/postgres.py
  U   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/testSQLAlchemy.py
  U   z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/util.py

-=-
Modified: z3c.sqlalchemy/trunk/CHANGES.txt
===================================================================
--- z3c.sqlalchemy/trunk/CHANGES.txt	2008-05-19 05:37:35 UTC (rev 86824)
+++ z3c.sqlalchemy/trunk/CHANGES.txt	2008-05-19 05:38:04 UTC (rev 86825)
@@ -1,3 +1,16 @@
+1.2.0 (unreleased)
+------------------
+
+  - now using zope.sqlalchemy for ZODB transaction integration
+
+  - internal class renaming
+
+  - remove PythonBaseWrapper, there is only *one* ZopeWrapper
+
+  - requires SQLAlchemy 0.4.6 or higher
+
+  - requires zope.sqlalchemy 0.1 or higher
+
 1.1.5 (08.05.2008)
 ------------------
 

Modified: z3c.sqlalchemy/trunk/README.txt
===================================================================
--- z3c.sqlalchemy/trunk/README.txt	2008-05-19 05:37:35 UTC (rev 86824)
+++ z3c.sqlalchemy/trunk/README.txt	2008-05-19 05:38:04 UTC (rev 86825)
@@ -22,7 +22,6 @@
 - no support for Zope 3 schemas 
 - no support for Archetypes schemas
 
-
 z3c.sqlachemy just tries to provide you with the basic functionalities you need
 to write SQLAlchemy-based applications with Zope 2/3. Higher-level
 functionalities like integration with Archetypes/Zope 3 schemas are subject to
@@ -33,7 +32,8 @@
 =============
 
 - Zope 2.8+, Zope 3.X
-- SQLAlchemy 0.4.0 or higher  (no support for SQLAlchemy 0.3) 
+- SQLAlchemy 0.4.6 or higher  (no support for SQLAlchemy 0.3) 
+- zope.sqlalchemy 0.1.0 or higher
 - Python 2.4+
 
 

Modified: z3c.sqlalchemy/trunk/buildout.cfg
===================================================================
--- z3c.sqlalchemy/trunk/buildout.cfg	2008-05-19 05:37:35 UTC (rev 86824)
+++ z3c.sqlalchemy/trunk/buildout.cfg	2008-05-19 05:38:04 UTC (rev 86825)
@@ -1,29 +1,7 @@
 [buildout]
-parts = plone zope2 instance
-eggs =
-develop =
+parts = test
+develop = .
 
-[plone]
-recipe = plone.recipe.plone
-
-[zope2]
-recipe = plone.recipe.zope2install
-url = ${plone:zope2-url}
-
-[instance]
-recipe = plone.recipe.zope2instance
-zope2-location = ${zope2:location}
-user = admin:admin
-http-port = 8080
-debug-mode = on
-verbose-security = on
-eggs =
-    ${buildout:eggs}
-    ${plone:eggs}
-zcml =
-
-products =
-    ${plone:products}
-    ${buildout:directory}/products
-
-
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.sqlalchemy [test]

Modified: z3c.sqlalchemy/trunk/setup.py
===================================================================
--- z3c.sqlalchemy/trunk/setup.py	2008-05-19 05:37:35 UTC (rev 86824)
+++ z3c.sqlalchemy/trunk/setup.py	2008-05-19 05:38:04 UTC (rev 86825)
@@ -7,10 +7,8 @@
 ##########################################################################
 
 
-import os
 from setuptools import setup, find_packages
 
-
 CLASSIFIERS = [
     'Development Status :: 5 - Production/Stable',
     'Intended Audience :: Developers',
@@ -48,7 +46,8 @@
       zip_safe=True,
       namespace_packages=['z3c'],
       install_requires=['setuptools',
-                        'SQLAlchemy>=0.4.0',
+                        'SQLAlchemy>=0.4.6',
+                        'zope.sqlalchemy',
 #                        'zope.component==3.3',
 #                        'zope.interface==3.3',
 #                        'zope.schema==3.3',

Modified: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/base.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/base.py	2008-05-19 05:37:35 UTC (rev 86824)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/base.py	2008-05-19 05:38:04 UTC (rev 86825)
@@ -6,52 +6,23 @@
 # and ZOPYX Ltd. & Co. KG, Tuebingen, Germany
 ##########################################################################
 
-import random
-import threading
-
-import sqlalchemy
-from sqlalchemy.engine.url import make_url
-from sqlalchemy.orm import sessionmaker
-
 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.mapper import LazyMapperCollection
+from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper, IModelProvider
 
-import transaction
-from transaction.interfaces import ISavepointDataManager, IDataManagerSavepoint
 
+from sqlalchemy import create_engine, MetaData
+from sqlalchemy.engine.url import make_url
+from sqlalchemy.orm import scoped_session, sessionmaker, relation
+from zope.sqlalchemy import ZopeTransactionExtension
 
-class SynchronizedThreadCache(object):
 
-    def __init__(self):
-        self.lock = threading.Lock()
-        self.cache = threading.local()
+class ZopeWrapper(object):
 
-    def set(self, id, d):
-        self.lock.acquire()
-        setattr(self.cache, id, d)
-        self.lock.release()
-
-    def get(self, id):
-        self.lock.acquire()
-        result = getattr(self.cache, id, None)
-        self.lock.release()
-        return result
-
-    def remove(self, id):
-        self.lock.acquire()
-        if hasattr(self.cache, id):
-            delattr(self.cache, id)           
-        self.lock.release()
-
-
-
-class BaseWrapper(object):
-
     implements(ISQLAlchemyWrapper)
 
     def __init__(self, dsn, model=None, transactional=True, engine_options={}, session_options={}, **kw):
@@ -82,7 +53,6 @@
         self.session_options = session_options
         self._model = None
         self._createEngine()
-        self._id = str(random.random()) # used as unique key for session/connection cache
 
         if model:
 
@@ -117,13 +87,23 @@
     @property
     def metadata(self):
         if not hasattr(self, '_v_metadata'):
-            self._v_metadata = sqlalchemy.MetaData(self._engine)
+            self._v_metadata = MetaData(self._engine)
         return self._v_metadata
 
     @property
     def session(self):
+        """ Return thread-local session """
         return self._sessionmaker()
 
+    @property
+    def connection(self):
+        """ Return underlying connection """
+        session = self.session
+        # Return the ConnectionFairy
+        return session.connection().connection
+        # instead of the raw connection
+        #return session.connection().connection.connection
+
     def registerMapper(self, mapper, name):
         self._mappers.registerMapper(mapper, name)
 
@@ -144,152 +124,10 @@
         return self._model
 
     def _createEngine(self):
-        self._engine = sqlalchemy.create_engine(self.dsn, **self.engine_options)
-        self._sessionmaker = sqlalchemy.orm.sessionmaker(bind=self._engine, 
-                                                         autoflush=True,
-                                                         transactional=True,
-                                                         **self.session_options)
-
-
-connection_cache = SynchronizedThreadCache()
-
-
-class SessionDataManager(object):
-    """ Wraps session into transaction context of Zope """
-
-    implements(ISavepointDataManager)
-
-    def __init__(self, connection, session, id, transactional=True):
-
-        self.connection = connection
-        self.session = session
-        self.transactional = True
-        self._id = id
-        self.transaction = None
-        if self.transactional:
-            self.transaction = connection.begin()
-
-    def abort(self, trans):
-
-        try:
-            if self.transaction is not None:
-                self.transaction.rollback()
-        # DM: done in "_cleanup" (similar untidy code at other places as well)
-##        self.session.clear()
-##        connection_cache.remove(self._id)
-        finally:
-            # ensure '_cleanup' is called even when 'rollback' causes an exception
-            self._cleanup()
-
-    def _flush(self):
-
-        # check if the session contains something flushable
-        if self.session.new or self.session.deleted or self.session.dirty:
-
-            # Check if a session-bound transaction has been created so far.
-            # If not, create a new transaction
-#            if self.transaction is None:
-#                self.transaction = connection.begin()
-
-            # Flush
-            self.session.flush()
-
-    def commit(self, trans):
-        self._flush()
-
-    def tpc_begin(self, trans):
-        pass
-
-    def tpc_vote(self, trans):
-        self._flush()
-
-    def tpc_finish(self, trans):
-
-        if self.transaction is not None:
-            self.transaction.commit()
-
-        self.session.clear()
-        self._cleanup()
-        
-
-    # DM: no need to duplicate this code (identical to "abort")
-##    def tpc_abort(self, trans):
-##        if self.transaction is not None:
-##            self.transaction.rollback()
-##        self._cleanup()
-    tpc_abort = abort
-
-    def sortKey(self):
-        return 'z3c.sqlalchemy_' + str(id(self))
-
-    def _cleanup(self):
-        self.session.clear()
-        if self.connection:
-            self.connection.close()
-            self.connection = None
-        connection_cache.remove(self._id)
-        # DM: maybe, we should set "transaction" to "None"?
-
-    def savepoint(self):
-        """ return a dummy savepoint """
-        return AlchemySavepoint()
-
-
-
-# taken from z3c.zalchemy
-
-class AlchemySavepoint(object):
-    """A dummy saveoint """
-
-    implements(IDataManagerSavepoint)
-
-    def __init__(self):
-        pass
-
-    def rollback(self):
-        pass
-
-
-
-class ZopeBaseWrapper(BaseWrapper):
-    """ A wrapper to be used from within Zope. It connects
-        the session with the transaction management of Zope.
-    """
-
-
-    def __getOrCreateConnectionCacheItem(self, cache_id):
-
-        cache_item = connection_cache.get(cache_id)
-
-        # return cached session if we are within the same transaction
-        # and same thread
-        if cache_item is not None:
-            return cache_item
-
-        # no cached session, let's create a new one
-        connection = self.engine.connect()
-        session = sessionmaker(connection)()
-                                          
-        # register a DataManager with the current transaction
-        transaction.get().join(SessionDataManager(connection, session, self._id))
-
-        # update thread-local cache
-        cache_item = dict(connection=connection, session=session)
-        connection_cache.set(self._id, cache_item)
-        return cache_item
-
-
-    @property
-    def session(self):
-        """ Return a (cached) session object for the current transaction """
-        return self.__getOrCreateConnectionCacheItem(self._id)['session']
-
-
-    @property
-    def connection(self):
-        """ This property is _private_ and only intented to be used
-            by SQLAlchemyDA and therefore it is not part of the 
-            public API. 
-        """
-    
-        return self.__getOrCreateConnectionCacheItem(self._id)['connection']
+        self._engine = create_engine(self.dsn, **self.engine_options)
+        self._sessionmaker = scoped_session(sessionmaker(bind=self._engine, 
+                                            transactional=True, 
+                                            autoflush=True, 
+                                            extension=ZopeTransactionExtension(),
+                                            **self.session_options
+                                            ))

Modified: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/postgres.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/postgres.py	2008-05-19 05:37:35 UTC (rev 86824)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/postgres.py	2008-05-19 05:38:04 UTC (rev 86825)
@@ -15,7 +15,7 @@
 from zope.interface import implements
 
 from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper
-from z3c.sqlalchemy.base import BaseWrapper, ZopeBaseWrapper
+from z3c.sqlalchemy.base import ZopeWrapper
 
 
 _cache = threading.local() # module-level cache 
@@ -68,12 +68,7 @@
         return _cache.ref_mapping
 
 
-class PythonPostgresWrapper(BaseWrapper, PostgresMixin):
-    """ Wrapper to be used with Python with extended
-        Postgres functionality.
-    """
-
-class ZopePostgresWrapper(ZopeBaseWrapper, PostgresMixin):
+class ZopePostgresWrapper(ZopeWrapper, PostgresMixin):
     """ A wrapper to be used from within Zope. It connects
         the session with the transaction management of Zope.
     """

Modified: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/testSQLAlchemy.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/testSQLAlchemy.py	2008-05-19 05:37:35 UTC (rev 86824)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/tests/testSQLAlchemy.py	2008-05-19 05:38:04 UTC (rev 86825)
@@ -14,7 +14,6 @@
 """
 
 import os
-import unittest
 import sqlalchemy
 
 from sqlalchemy import MetaData, Integer, String, Column, Table
@@ -22,13 +21,13 @@
 from zope.interface.verify import verifyClass
 
 from z3c.sqlalchemy.interfaces import ISQLAlchemyWrapper, IModel
-from z3c.sqlalchemy.postgres import PythonPostgresWrapper,  ZopePostgresWrapper
-from z3c.sqlalchemy.base import BaseWrapper
+from z3c.sqlalchemy.postgres import ZopePostgresWrapper
 from z3c.sqlalchemy.mapper import MappedClassBase
 from z3c.sqlalchemy import createSAWrapper, Model, registerSAWrapper, getSAWrapper
+from Testing.ZopeTestCase import ZopeTestCase
 
 
-class WrapperTests(unittest.TestCase):
+class WrapperTests(ZopeTestCase):
 
     def setUp(self):
 
@@ -38,25 +37,17 @@
 
         users = Table('users', metadata,
                       Column('id', Integer, primary_key=True),
-                      Column('firstname', String),
-                      Column('lastname', String))
+                      Column('firstname', String(255)),
+                      Column('lastname', String(255)))
 
         skill = Table('skills', metadata,
-                      Column('id', Integer, primary_key=True),
+                      Column('user_id', Integer, primary_key=True),
                       Column('user_id', Integer),
-                      Column('name', String))
+                      Column('name', String(255)))
 
+        metadata.drop_all()
         metadata.create_all()
 
-
-    def testIFaceBaseWrapper (self):
-        verifyClass(ISQLAlchemyWrapper , BaseWrapper)
-
-
-    def testIFacePythonPostgres(self):
-        verifyClass(ISQLAlchemyWrapper , PythonPostgresWrapper)
-
-
     def testIFaceZopePostgres(self):
         verifyClass(ISQLAlchemyWrapper , ZopePostgresWrapper)
 
@@ -77,7 +68,7 @@
         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).order_by(User.c.id).all()
         self.assertEqual(len(rows), 2)
         row1 = rows[0]
@@ -173,12 +164,38 @@
         User = db.getMapper('users')
         session = db.session
         session.save(User(id=1,firstname='foo', lastname='bar'))
-
+        session.flush()
         user = session.query(User).filter_by(firstname='foo')[0]
         Skill = user.getMapper('skills')
         user.skills.append(Skill(id=1, name='Zope'))
         session.flush()
 
+    def testCheckConnection(self):
+        """ Check access to low-level connection """
+        db = createSAWrapper(self.dsn)
+        conn = db.connection               
+        cursor = conn.cursor()
+        cursor.execute('select * from users')
+        rows = cursor.fetchall()
+        self.assertEqual(len(rows), 0)
+
+    def testConnectionPlusSession(self):
+        """ Check access to low-level connection """
+        db = createSAWrapper(self.dsn)
+
+        User = db.getMapper('users')
+        session = db.session
+        session.save(User(id=1, firstname='udo', lastname='juergens'))
+        session.save(User(id=2, firstname='heino', lastname='n/a'))
+        session.flush()
+
+        conn = db.connection               
+        cursor = conn.cursor()
+        cursor.execute('select * from users')
+        rows = cursor.fetchall()
+        self.assertEqual(len(rows), 2)
+
+
 def test_suite():
     from unittest import TestSuite, makeSuite
     suite = TestSuite()

Modified: z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/util.py
===================================================================
--- z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/util.py	2008-05-19 05:37:35 UTC (rev 86824)
+++ z3c.sqlalchemy/trunk/src/z3c/sqlalchemy/util.py	2008-05-19 05:38:04 UTC (rev 86825)
@@ -14,21 +14,19 @@
 
 from sqlalchemy.engine.url import make_url
 
-from zope.component import getService, getGlobalServices, getUtilitiesFor, getUtility
-from zope.component.utility import GlobalUtilityService
+from zope.component import getUtilitiesFor, getUtility
 from zope.component.interfaces import IUtilityService, ComponentLookupError
-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, ZopeBaseWrapper
+from z3c.sqlalchemy.postgres import ZopePostgresWrapper
+from z3c.sqlalchemy.base import ZopeWrapper
 
 __all__ = ('createSQLAlchemyWrapper', 'registerSQLAlchemyWrapper', 'allRegisteredSQLAlchemyWrappers', 'getSQLAlchemyWrapper',
            'createSAWrapper', 'registerSAWrapper', 'allRegisteredSAWrappers', 'getSAWrapper', 'allSAWrapperNames')
 
 registeredWrappers = {}
 
-def createSAWrapper(dsn, model=None, forZope=False, name=None, transactional=True, 
+def createSAWrapper(dsn, model=None, name=None, transactional=True, 
                     engine_options={}, session_options={}, **kw):
     """ Convenience method to generate a wrapper for a DSN and a model.
         This method hides all database related magic from the user. 
@@ -39,9 +37,6 @@
         a named utility implementing IModelProvider or a method/callable returning an
         instance of model.Model.
 
-        'forZope' - set this to True in order to obtain a Zope-transaction-aware
-        wrapper.
-
         'transactional' - True|False, only used for SQLAlchemyDA *don't change it*
 
         'name' can be set to register the wrapper automatically  in order
@@ -57,10 +52,10 @@
     url = make_url(dsn)
     driver = url.drivername
 
-    klass = forZope and ZopeBaseWrapper or BaseWrapper
+    klass = ZopeWrapper 
 
     if driver == 'postgres':
-        klass = forZope and ZopePostgresWrapper or PythonPostgresWrapper
+        klass = ZopePostgresWrapper
 
     wrapper = klass(dsn, model, 
                     transactional=transactional, 



More information about the Checkins mailing list