[Checkins] SVN: zc.zodbwsgi/branches/dev/ Initial implementation

Jim Fulton jim at zope.com
Tue Feb 16 17:54:40 EST 2010


Log message for revision 109081:
  Initial implementation
  

Changed:
  U   zc.zodbwsgi/branches/dev/README.txt
  U   zc.zodbwsgi/branches/dev/buildout.cfg
  U   zc.zodbwsgi/branches/dev/setup.py
  A   zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/
  A   zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/README.txt
  A   zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/__init__.py
  A   zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/tests.py

-=-
Modified: zc.zodbwsgi/branches/dev/README.txt
===================================================================
--- zc.zodbwsgi/branches/dev/README.txt	2010-02-16 22:52:00 UTC (rev 109080)
+++ zc.zodbwsgi/branches/dev/README.txt	2010-02-16 22:54:39 UTC (rev 109081)
@@ -1,14 +1 @@
-Title Here
-**********
-
-
-To learn more, see
-
-
-Changes
-*******
-
-0.1 (yyyy-mm-dd)
-================
-
-Initial release
+See src/zc/zodbwsgi/README.txt

Modified: zc.zodbwsgi/branches/dev/buildout.cfg
===================================================================
--- zc.zodbwsgi/branches/dev/buildout.cfg	2010-02-16 22:52:00 UTC (rev 109080)
+++ zc.zodbwsgi/branches/dev/buildout.cfg	2010-02-16 22:54:39 UTC (rev 109081)
@@ -4,7 +4,7 @@
 
 [test]
 recipe = zc.recipe.testrunner
-eggs = 
+eggs = zc.zodbwsgi [test]
 
 [py]
 recipe = zc.recipe.egg

Modified: zc.zodbwsgi/branches/dev/setup.py
===================================================================
--- zc.zodbwsgi/branches/dev/setup.py	2010-02-16 22:52:00 UTC (rev 109080)
+++ zc.zodbwsgi/branches/dev/setup.py	2010-02-16 22:54:39 UTC (rev 109081)
@@ -11,24 +11,32 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-name, version = 'zc.', '0'
+name, version = 'zc.zodbwsgi', '0'
 
-install_requires = ['setuptools']
-extras_require = dict(test=['zope.testing'])
+install_requires = ['setuptools', 'repoze.retry', 'ZConfig', 'ZODB3']
+extras_require = dict(
+    test=['zope.testing', 'manuel', 'PasteDeploy', 'webtest'])
 
 entry_points = """
+[paste.filter_app_factory]
+main = zc.zodbwsgi:Factory
 """
 
 from setuptools import setup
+import os
 
+long_description = open(
+    os.path.join(*(['src'] + name.split('.') + ['README.txt']))
+    ).read()
+
 setup(
     author = 'Jim Fulton',
     author_email = 'jim at zope.com',
     license = 'ZPL 2.1',
 
     name = name, version = version,
-    long_description=open('README.txt').read(),
-    description = open('README.txt').read().strip().split('\n')[0],
+    long_description=long_description,
+    description = long_description.strip().split('\n')[0],
     packages = [name.split('.')[0], name],
     namespace_packages = [name.split('.')[0]],
     package_dir = {'': 'src'},

Added: zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/README.txt
===================================================================
--- zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/README.txt	                        (rev 0)
+++ zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/README.txt	2010-02-16 22:54:39 UTC (rev 109081)
@@ -0,0 +1,323 @@
+WSGI Middleware for Managing ZODB Database Conections
+=====================================================
+
+The zc.zodbwsgi provides middleware for managing connections to a ZODB
+database. It combines several features into a single middleware
+component:
+
+- database configuration
+- database initialization
+- connection management
+- optional transaction management
+- optional request retry on conflict errors (using repoze.retry)
+
+It is designed to work with paste deployment and provides a
+"filter_app_factory" entry point, named "main".
+
+A number of configuration options are provided. Option values are
+strings.
+
+configuration
+   A required ZConfig formatted ZODB database configuration
+
+   If multiple databases are defined, they will define a
+   multi-database. Connections will be to the first defined database.
+
+initializer
+   An optional database initialization function of the form
+   ``module:expression``
+
+key
+   An optional name of a WSGI environment key for database connections
+
+   This defaults to "zodb.connection".
+
+transaction_management
+   An optional flag (either "true" or "false") indicating whether the
+   middleware manages transactions
+
+   Transaction management is enabled by default.
+
+transaction_key
+   An optional name of a WSGI environment key for transaction managers
+
+   This defaults to "transaction.manager". The key will only be
+   present if transaction management is enabled.
+
+retry
+   An optional retry count
+
+   The default is "3", indicating that requests will be retried up to
+   3 times.  Use "0" to disable retries.
+
+   Note that when retry is not "0", request bodies will be buffered.
+
+Let's look at some examples.
+
+First we define an demonstration "application" that we can pass to our
+factory::
+
+    import transaction, ZODB.POSException
+    from sys import stdout
+
+    class demo_app:
+        def __init__(self, default):
+            pass
+        def __call__(self, environ, start_response):
+            start_response('200 OK', [('content-type', 'text/html')])
+            root = environ['zodb.connection'].root()
+            path = environ['PATH_INFO']
+            if path == '/inc':
+                root['x'] = root.get('x', 0) + 1
+                if 'transaction.manager' in environ:
+                    environ['transaction.manager'].get().note('path: %r' % path)
+                else:
+                    transaction.commit() # We have to commit our own!
+            elif path == '/conflict':
+                print >>stdout, 'Conflict!'
+                raise ZODB.POSException.ConflictError
+
+            return [repr(root)]
+
+.. -> src
+
+   >>> import zc.zodbwsgi.tests
+   >>> exec(src, zc.zodbwsgi.tests.__dict__)
+
+Now, we'll define our application factory using a paste deployment
+configuration::
+
+   [app:main]
+   paste.app_factory = zc.zodbwsgi.tests:demo_app
+   filter-with = zodb
+
+   [filter:zodb]
+   use = egg:zc.zodbwsgi
+   configuration =
+      <zodb>
+        <demostorage>
+        </demostorage>
+      </zodb>
+
+.. -> src
+
+    >>> open('paste.ini', 'w').write(src)
+
+Here, for demonstration purposes, we used an in-memory demo storage.
+
+Now, we'll create an application with paste:
+
+    >>> import paste.deploy, os
+    >>> app = paste.deploy.loadapp('config:'+os.path.abspath('paste.ini'))
+
+The resulting applications has a database attribute (mainly for
+testing) with the created database.
+Being newly initialized, the database is empty:
+
+    >>> conn = app.database.open()
+    >>> conn.root()
+    {}
+
+Let's do an "increment" request.
+
+    >>> import webtest
+    >>> testapp = webtest.TestApp(app)
+    >>> testapp.get('/inc')
+    <200 OK text/html body="{'x': 1}">
+
+Now, if we look at the database, we see that there's now data in the
+root object:
+
+    >>> conn.sync()
+    >>> conn.root()
+    {'x': 1}
+
+We can supply a database initialization function using the initializer
+option.  Let's define an initialization function::
+
+    import transaction
+
+    def initialize_demo_db(db):
+        conn = db.open()
+        conn.root()['x'] = 100
+        transaction.commit()
+        conn.close()
+
+.. -> src
+
+   >>> exec(src, zc.zodbwsgi.tests.__dict__)
+
+and update our paste configuration to use it::
+
+   [app:main]
+   paste.app_factory = zc.zodbwsgi.tests:demo_app
+   filter-with = zodb
+
+   [filter:zodb]
+   use = egg:zc.zodbwsgi
+   configuration =
+      <zodb>
+        <demostorage>
+        </demostorage>
+      </zodb>
+
+   initializer = zc.zodbwsgi.tests:initialize_demo_db
+
+.. -> src
+
+    >>> open('paste.ini', 'w').write(src)
+
+Now, when we use the application, we see the impact of the
+initializer:
+
+    >>> app = paste.deploy.loadapp('config:'+os.path.abspath('paste.ini'))
+    >>> testapp = webtest.TestApp(app)
+    >>> testapp.get('/inc')
+    <200 OK text/html body="{'x': 101}">
+
+.. Our application updated transaction meta data when called under
+   transaction control.
+
+    >>> app.database.history(conn.root()._p_oid, 1)[0]['description']
+    "path: '/inc'"
+
+Sometimes, you may not want the middleware to control transactions.
+You might do this if your application used multiple databases,
+including non-ZODB databases [#multidb]_.  You can suppress
+transaction management by supplying a value of "false" for the
+transaction_management option::
+
+   [app:main]
+   paste.app_factory = zc.zodbwsgi.tests:demo_app
+   filter-with = zodb
+
+   [filter:zodb]
+   use = egg:zc.zodbwsgi
+   configuration =
+      <zodb>
+        <demostorage>
+        </demostorage>
+      </zodb>
+
+   initializer = zc.zodbwsgi.tests:initialize_demo_db
+   transaction_management = false
+
+.. -> src
+
+    >>> open('paste.ini', 'w').write(src)
+    >>> app = paste.deploy.loadapp('config:'+os.path.abspath('paste.ini'))
+    >>> testapp = webtest.TestApp(app)
+    >>> testapp.get('/inc')
+    <200 OK text/html body="{'x': 101}">
+
+    >>> app.database.history('\0'*8, 1)[0]['description']
+    ''
+
+By default, zc.zodbwsgi adds ``repoze.retry`` middleware to retry requests
+when there are conflict errors:
+
+    >>> import ZODB.POSException
+    >>> app = paste.deploy.loadapp('config:'+os.path.abspath('paste.ini'))
+    >>> testapp = webtest.TestApp(app)
+    >>> try: testapp.get('/conflict')
+    ... except ZODB.POSException.ConflictError: pass
+    ... else: print 'oops'
+    Conflict!
+    Conflict!
+    Conflict!
+    Conflict!
+
+Here we can see that the request was retried 3 times.
+
+We can suppress this by supplying a value of "0" for the retry option::
+
+   [app:main]
+   paste.app_factory = zc.zodbwsgi.tests:demo_app
+   filter-with = zodb
+
+   [filter:zodb]
+   use = egg:zc.zodbwsgi
+   configuration =
+      <zodb>
+        <demostorage>
+        </demostorage>
+      </zodb>
+
+   retry = 0
+
+.. -> src
+
+    >>> open('paste.ini', 'w').write(src)
+
+Now, if we run the app, the request won't be retried:
+
+    >>> app = paste.deploy.loadapp('config:'+os.path.abspath('paste.ini'))
+    >>> testapp = webtest.TestApp(app)
+    >>> try: testapp.get('/conflict')
+    ... except ZODB.POSException.ConflictError: pass
+    ... else: print 'oops'
+    Conflict!
+
+.. Other tests of corner cases:
+
+  ::
+
+    class demo_app:
+        def __init__(self, default):
+            pass
+        def __call__(self, environ, start_response):
+            start_response('200 OK', [('content-type', 'text/html')])
+            root = environ['connection'].root()
+            path = environ['PATH_INFO']
+            if path == '/inc':
+                root['x'] = root.get('x', 0) + 1
+                environ['manager'].get().note('path: %r' % path)
+
+            return [repr(root)]
+
+  .. -> src
+
+   >>> exec(src, zc.zodbwsgi.tests.__dict__)
+
+  ::
+
+   [app:main]
+   paste.app_factory = zc.zodbwsgi.tests:demo_app
+   filter-with = zodb
+
+   [filter:zodb]
+   use = egg:zc.zodbwsgi
+   configuration =
+      <zodb>
+        <demostorage>
+        </demostorage>
+      </zodb>
+
+   key = connection
+   transaction_key = manager
+
+  .. -> src
+
+    >>> open('paste.ini', 'w').write(src)
+    >>> app = paste.deploy.loadapp('config:'+os.path.abspath('paste.ini'))
+    >>> testapp = webtest.TestApp(app)
+    >>> testapp.get('/inc')
+    <200 OK text/html body="{'x': 1}">
+
+
+Changes
+=======
+
+0.1 (2010-02-dd)
+----------------
+
+Initial release
+
+
+
+.. [#multidb] If you want to use multiple ZODB databases, you can
+   simply define them in your configuration option.  Just make sure to
+   give them names.  When you want to access a database, use the
+   ``get_connection`` method on the connection in the environment::
+
+      foo_conn = environ['zodb.connection'].get_connection('foo')


Property changes on: zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/README.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Added: zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/__init__.py
===================================================================
--- zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/__init__.py	                        (rev 0)
+++ zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/__init__.py	2010-02-16 22:54:39 UTC (rev 109081)
@@ -0,0 +1,83 @@
+##############################################################################
+#
+# Copyright 2010 Zope Foundation 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.
+#
+##############################################################################
+
+import repoze.retry
+import transaction
+import ZODB.config
+
+booleans = dict(true=True, false=False)
+
+def Factory(application, default,
+            configuration,
+            initializer=None,
+            key=None,
+            transaction_management=None,
+            transaction_key=None,
+            retry=None,
+            max_memory_retry_buffer_size=1<<20,
+            ):
+    db = ZODB.config.databaseFromString(configuration)
+
+    initializer = initializer or default.get('initializer')
+    if initializer:
+        module, expr = initializer.split(':', 1)
+        if module:
+            d = __import__(module, {}, {}, ['*']).__dict__
+        else:
+            d={}
+        initializer = eval(expr, d)
+        initializer(db)
+
+    key = key or default.get('key', 'zodb.connection')
+
+    transaction_management = booleans[
+        transaction_management or default.get(
+            'transaction_management', 'true').lower()]
+
+    if transaction_management:
+        transaction_key = transaction_key or default.get(
+            'transaction_key', 'transaction.manager')
+        def dbapp(environ, start_response):
+            tm = environ[transaction_key] = transaction.TransactionManager()
+            conn = environ[key] = db.open(tm)
+            try:
+                try:
+                    result = application(environ, start_response)
+                except:
+                    tm.get().abort()
+                    raise
+                else:
+                    tm.get().commit()
+                return result
+            finally:
+                conn.close()
+                del environ[transaction_key]
+                del environ[key]
+
+    else:
+        def dbapp(environ, start_response):
+            conn = environ[key] = db.open()
+            try:
+                return application(environ, start_response)
+            finally:
+                conn.close()
+                del environ[key]
+
+    retry = int(retry or default.get('retry', '3'))
+    if retry > 0:
+        dbapp = repoze.retry.Retry(dbapp, tries=retry+1)
+    dbapp.database = db
+
+    return dbapp
+


Property changes on: zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/__init__.py
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/tests.py
===================================================================
--- zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/tests.py	                        (rev 0)
+++ zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/tests.py	2010-02-16 22:54:39 UTC (rev 109081)
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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 unittest
+import zope.testing.setupstack
+import manuel.capture
+import manuel.doctest
+import manuel.testing
+
+def test_suite():
+    return unittest.TestSuite((
+        manuel.testing.TestSuite(
+            manuel.doctest.Manuel() + manuel.capture.Manuel(),
+            'README.txt',
+            setUp=zope.testing.setupstack.setUpDirectory,
+            tearDown=zope.testing.setupstack.tearDown,
+            )
+        ))


Property changes on: zc.zodbwsgi/branches/dev/src/zc/zodbwsgi/tests.py
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native



More information about the checkins mailing list