[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