[Checkins] SVN: z3c.saconfig/ Initial import of z3c.saconfig, a system to configure SQLAlchemy sessions.

Martijn Faassen faassen at infrae.com
Thu Jun 19 14:41:25 EDT 2008


Log message for revision 87560:
  Initial import of z3c.saconfig, a system to configure SQLAlchemy sessions.
  

Changed:
  A   z3c.saconfig/
  A   z3c.saconfig/trunk/
  A   z3c.saconfig/trunk/CHANGES.txt
  A   z3c.saconfig/trunk/README.txt
  A   z3c.saconfig/trunk/bootstrap.py
  A   z3c.saconfig/trunk/buildout.cfg
  A   z3c.saconfig/trunk/setup.py
  A   z3c.saconfig/trunk/src/
  A   z3c.saconfig/trunk/src/z3c/
  A   z3c.saconfig/trunk/src/z3c/__init__.py
  A   z3c.saconfig/trunk/src/z3c/saconfig/
  A   z3c.saconfig/trunk/src/z3c/saconfig/README.txt
  A   z3c.saconfig/trunk/src/z3c/saconfig/__init__.py
  A   z3c.saconfig/trunk/src/z3c/saconfig/interfaces.py
  A   z3c.saconfig/trunk/src/z3c/saconfig/scopedsession.py
  A   z3c.saconfig/trunk/src/z3c/saconfig/tests.py
  A   z3c.saconfig/trunk/src/z3c/saconfig/utility.py

-=-
Added: z3c.saconfig/trunk/CHANGES.txt
===================================================================
--- z3c.saconfig/trunk/CHANGES.txt	                        (rev 0)
+++ z3c.saconfig/trunk/CHANGES.txt	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,7 @@
+z3c.saconfig
+************
+
+0.1 (unreleased)
+================
+
+* Initial public release.

Added: z3c.saconfig/trunk/README.txt
===================================================================
--- z3c.saconfig/trunk/README.txt	                        (rev 0)
+++ z3c.saconfig/trunk/README.txt	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,5 @@
+=================
+ zope.sqlalchemy
+=================
+
+Zope/SQLAlchemy transaction integration. See src/zope/sqlalchemy/README.txt.

Added: z3c.saconfig/trunk/bootstrap.py
===================================================================
--- z3c.saconfig/trunk/bootstrap.py	                        (rev 0)
+++ z3c.saconfig/trunk/bootstrap.py	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# 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.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id: bootstrap.py 71258 2006-11-21 22:22:48Z jim $
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                     ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+    cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+    os.P_WAIT, sys.executable, sys.executable,
+    '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+    dict(os.environ,
+         PYTHONPATH=
+         ws.find(pkg_resources.Requirement.parse('setuptools')).location
+         ),
+    ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)

Added: z3c.saconfig/trunk/buildout.cfg
===================================================================
--- z3c.saconfig/trunk/buildout.cfg	                        (rev 0)
+++ z3c.saconfig/trunk/buildout.cfg	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,12 @@
+[buildout]
+develop = . zope.sqlalchemy
+parts = test scripts
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = z3c.saconfig
+
+[scripts]
+recipe = zc.recipe.egg
+eggs = z3c.saconfig
+interpreter = py
\ No newline at end of file

Added: z3c.saconfig/trunk/setup.py
===================================================================
--- z3c.saconfig/trunk/setup.py	                        (rev 0)
+++ z3c.saconfig/trunk/setup.py	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,43 @@
+from setuptools import setup, find_packages
+import sys, os
+
+def read(*rnames):
+    return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+long_description = ""
+
+setup(name='z3c.saconfig',
+      version='0.1dev',
+      description="Minimal SQLAlchemy ORM session configuration for Zope",
+      long_description=long_description,
+      # Get more strings from http://www.python.org/pypi?%3Aaction=list_classifiers
+      classifiers=[
+        "Framework :: Zope3",
+        "Programming Language :: Python",
+        "License :: OSI Approved :: Zope Public License",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        ],
+      keywords='',
+      author='Martijn Faassen',
+      author_email='faassen at startifact.com',
+      url='http://pypi.python.org/pypi/z3c.saconfig',
+      license='ZPL 2.1',
+      packages=find_packages('src'),
+      package_dir = {'':'src'},
+      namespace_packages=['z3c'],
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=[
+          # -*- Extra requirements: -*-
+          'setuptools',
+          'zope.sqlalchemy',
+          'zope.interface',
+          'zope.component',
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+      """,
+      extras_require = dict(
+              test = ['zope.testing'],
+              ),
+      )

Added: z3c.saconfig/trunk/src/z3c/__init__.py
===================================================================
--- z3c.saconfig/trunk/src/z3c/__init__.py	                        (rev 0)
+++ z3c.saconfig/trunk/src/z3c/__init__.py	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,6 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)

Added: z3c.saconfig/trunk/src/z3c/saconfig/README.txt
===================================================================
--- z3c.saconfig/trunk/src/z3c/saconfig/README.txt	                        (rev 0)
+++ z3c.saconfig/trunk/src/z3c/saconfig/README.txt	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,117 @@
+z3c.saconfig
+************
+
+Introduction
+============
+
+This aim of this package is to offer a simple but flexible way to
+configure SQLAlchemy's scoped session support using the Zope 3
+component architecture. This package is based on ``zope.sqlalchemy``, which
+offers transaction integration between Zope and SQLAlchemy.
+
+GloballyScopedSession
+=====================
+
+The simplest way to set up SQLAlchemy for Zope is to have a single
+thread-scoped session that's global to your entire Zope
+instance. Multiple applications will all share this session. The
+engine is set up globally, in code.
+
+We use the SQLAlchemy ``sqlalchemy.ext.declarative`` extension to
+define some tables and classes::
+
+  >>> from sqlalchemy import *
+  >>> from sqlalchemy.ext.declarative import declarative_base
+  >>> from sqlalchemy.orm import relation
+
+  >>> Base = declarative_base()
+  >>> class User(Base):
+  ...     __tablename__ = 'test_users'
+  ...     id = Column('id', Integer, primary_key=True)
+  ...     name = Column('name', String(50))
+  ...     addresses = relation("Address", backref="user")
+  >>> class Address(Base):
+  ...     __tablename__ = 'test_addresses'
+  ...     id = Column('id', Integer, primary_key=True)
+  ...     email = Column('email', String(50))
+  ...     user_id = Column('user_id', Integer, ForeignKey('test_users.id'))
+
+We now set up an engine globally with our test DSN::
+
+  >>> engine = create_engine(TEST_DSN, convert_unicode=True)
+
+And we create the tables in our test database::
+
+  >>> Base.metadata.create_all(engine)
+
+So far this example doesn't differ any from the way
+``zope.sqlalchemy`` operates. The difference is in how we set up the
+session and use it. We'll use the ``GloballyScopedSession`` utility
+to implement our session creation::
+
+  >>> from z3c.saconfig import GloballyScopedSession
+
+We give the constructor to ``GloballyScopedSession`` the parameters
+you'd normally give to ``sqlalchemy.orm.create_session``, or
+``sqlalchemy.orm.sessionmaker``::
+
+  >>> utility = GloballyScopedSession(
+  ...   bind=engine, 
+  ...   twophase=TEST_TWOPHASE)
+
+``GloballyScopedSession`` automatically sets up the ``autocommit``,
+``autoflush`` and ``extension`` parameters to be the right ones for
+Zope integration, so normally you wouldn't need to supply these, but
+you could pass in your own if you do need it.
+
+We now register this as an ``IScopedSession`` utility with
+``zope.component``. Normally you'd use either ZCML or Grok to do this
+confirmation, but we'll do it manually here::
+  
+  >>> from zope import component
+  >>> from z3c.saconfig.interfaces import IScopedSession
+  >>> component.provideUtility(utility, provides=IScopedSession)
+ 
+We can now use the ``Session`` object create a session which
+will behave according to the utility we provided::
+
+  >>> from z3c.saconfig import Session
+  >>> session = Session()
+
+Now things go the usual ``zope.sqlalchemy`` way, which is like
+``SQLAlchemy`` except you can use Zope's ``transaction`` module::
+
+  >>> session.query(User).all()
+  []    
+  >>> import transaction
+  >>> session.save(User(name='bob'))
+  >>> transaction.commit()
+
+  >>> session = Session()
+  >>> bob = session.query(User).all()[0]
+  >>> bob.name
+  u'bob'
+  >>> bob.addresses
+  []
+
+Running the tests
+=================
+
+This package can be checked out from
+svn://svn.zope.org/repos/main. You can then execute the buildout to
+download and install the requirements and install the test
+runner. Using your desired python run:
+
+$ python bootstrap.py
+
+This will download the dependent packages and setup the test script, which may
+be run with:
+
+$ bin/test
+
+To enable testing with your own database set the TEST_DSN environment
+variable to your sqlalchemy database dsn. Two-phase commit behaviour
+may be tested by setting the TEST_TWOPHASE variable to a non empty
+string. e.g:
+
+$ TEST_DSN=postgres://test:test@localhost/test TEST_TWOPHASE=True bin/test

Added: z3c.saconfig/trunk/src/z3c/saconfig/__init__.py
===================================================================
--- z3c.saconfig/trunk/src/z3c/saconfig/__init__.py	                        (rev 0)
+++ z3c.saconfig/trunk/src/z3c/saconfig/__init__.py	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,2 @@
+from z3c.saconfig.scopedsession import Session
+from z3c.saconfig.utility import GloballyScopedSession

Added: z3c.saconfig/trunk/src/z3c/saconfig/interfaces.py
===================================================================
--- z3c.saconfig/trunk/src/z3c/saconfig/interfaces.py	                        (rev 0)
+++ z3c.saconfig/trunk/src/z3c/saconfig/interfaces.py	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,26 @@
+from zope.interface import Interface
+
+class IScopedSession(Interface):
+    """A utility that plugs into SQLAlchemy's scoped session machinery.
+
+    The idea is that you'd either register a IScopedSession utility globally,
+    for simple configurations, or locally, if you want to have the ability
+    to transparently use a different engine and session configuration per
+    database.
+    """
+    def session_factory():
+        """Create a SQLAlchemy session.
+
+        Typically you'd use sqlalchemy.orm.create_session to create
+        the session here.
+        """
+
+    def scopefunc():
+        """Determine the scope of the session.
+
+        This can be used to scope the session per thread, per Zope 3 site,
+        or otherwise. Return an immutable value to scope the session,
+        like a thread id, or a tuple with thread id and application id.
+        """
+
+

Added: z3c.saconfig/trunk/src/z3c/saconfig/scopedsession.py
===================================================================
--- z3c.saconfig/trunk/src/z3c/saconfig/scopedsession.py	                        (rev 0)
+++ z3c.saconfig/trunk/src/z3c/saconfig/scopedsession.py	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,24 @@
+from z3c.saconfig.interfaces import IScopedSession
+
+from sqlalchemy.orm import scoped_session
+from zope import component
+
+def session_factory():
+    """This is used by scoped session to create a new Session object.
+
+    It delegates to a IScopedSession utility.
+    """
+    utility = component.getUtility(IScopedSession)
+    return utility.session_factory()
+
+def scopefunc():
+    """This is used by scoped session to distinguish between sessions.
+
+    It delegates to a IScopedSession utility.
+    """
+    utility = component.getUtility(IScopedSession)
+    return utility.scopefunc()
+
+# this is framework central configuration. Use a IScopedSession utility
+# to define behavior.
+Session = scoped_session(session_factory, scopefunc)

Added: z3c.saconfig/trunk/src/z3c/saconfig/tests.py
===================================================================
--- z3c.saconfig/trunk/src/z3c/saconfig/tests.py	                        (rev 0)
+++ z3c.saconfig/trunk/src/z3c/saconfig/tests.py	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,40 @@
+# You may want to run the tests with your database. To do so set the
+# environment variable TEST_DSN to the connection url. e.g.:
+#
+# export TEST_DSN=postgres://plone:plone@localhost/test
+# export TEST_DSN=mssql://plone:plone@/test?dsn=mydsn
+#
+# To test in twophase commit mode export TEST_TWOPHASE=True 
+#
+# NOTE: The sqlite that ships with Mac OS X 10.4 is buggy.
+# Install a newer version (3.5.6) and rebuild pysqlite2 against it.
+
+
+import unittest
+import doctest
+import os
+
+from zope.testing import cleanup
+
+TEST_TWOPHASE = bool(os.environ.get('TEST_TWOPHASE'))
+TEST_DSN = os.environ.get('TEST_DSN', 'sqlite:///:memory:')
+
+def tearDownReadMe(test):
+    # clean up Zope
+    cleanup.cleanUp()
+
+    # clean up SQLAlchemy
+    Base = test.globs['Base']
+    engine = test.globs['engine']
+    Base.metadata.drop_all(engine)
+
+def test_suite():
+    optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
+
+    suite = unittest.TestSuite()
+    suite.addTest(doctest.DocFileSuite(
+        'README.txt',
+        optionflags=optionflags,
+        tearDown=tearDownReadMe,
+        globs={'TEST_DSN': TEST_DSN, 'TEST_TWOPHASE': TEST_TWOPHASE}))
+    return suite

Added: z3c.saconfig/trunk/src/z3c/saconfig/utility.py
===================================================================
--- z3c.saconfig/trunk/src/z3c/saconfig/utility.py	                        (rev 0)
+++ z3c.saconfig/trunk/src/z3c/saconfig/utility.py	2008-06-19 18:41:24 UTC (rev 87560)
@@ -0,0 +1,51 @@
+"""
+Some reusable, standard implementations of IScopedSession.
+"""
+
+import thread
+from zope.interface import implements
+import sqlalchemy
+from zope.sqlalchemy import ZopeTransactionExtension
+
+from z3c.saconfig.interfaces import IScopedSession
+
+class GloballyScopedSession(object):
+    """A globally scoped session.
+
+    Register this as a global utility to have just one kind of session
+    per Zope instance. All applications in this instance will share the
+    same session.
+
+    To register as a global utility you may need to register it with
+    a custom factory, or alternatively subclass it and override __init__
+    to pass the right arguments to the superclasses __init__.
+    """
+    implements(IScopedSession)
+
+    def __init__(self, **kw):
+        """Pass keywords arguments for sqlalchemy.orm.create_session.
+
+        Note that GloballyScopedSesssion does have different defaults than
+        ``create_session`` for various parameters where it makes sense
+        for Zope integration, namely:
+
+        autocommit = False
+        autoflush = True
+        extension = ZopeTransactionExtension()
+
+        Normally you wouldn't pass these in, but if you have the need
+        to override them, you could.
+        """
+        if 'autocommit' not in kw:
+            kw['autocommit'] = False
+        if 'autoflush' not in kw:
+            kw['autoflush'] = True
+        if 'extension' not in kw:
+            kw['extension'] = ZopeTransactionExtension()
+        self.kw = kw
+
+    def session_factory(self):
+        return sqlalchemy.orm.create_session(**self.kw)
+    
+    def scopefunc(self):
+        return thread.get_ident()



More information about the Checkins mailing list