[Checkins] SVN: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/ added tests of retry and requestsetup
Shane Hathaway
shane at hathawaymix.org
Tue Feb 17 23:48:43 EST 2009
Log message for revision 96662:
added tests of retry and requestsetup
Changed:
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/requestsetup.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/retry.py
A Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/requestsetup.txt
A Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/retry.txt
A Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/tests.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/configure.zcml
-=-
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/requestsetup.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/requestsetup.py 2009-02-18 03:02:05 UTC (rev 96661)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/requestsetup.py 2009-02-18 04:48:42 UTC (rev 96662)
@@ -18,7 +18,9 @@
from zope.configuration.exceptions import ConfigurationError
from zope.httpform import FormParser
from zope.i18n.interfaces import IUserPreferredCharsets
+from zope.i18n.interfaces import IUserPreferredLanguages
from zope.interface import implements
+from zope.publisher.interfaces.browser import IBrowserRequest
from zope.publisher.interfaces import IWSGIApplication
from zope.testing import cleanup
@@ -80,8 +82,8 @@
setDefaultSkin(request)
-class ProcessForm(object):
- """WSGI middleware that processes HTML form data.
+class ParseForm(object):
+ """WSGI middleware that parses HTML form data.
This step is separate from request creation so that the
error handling step can catch form data errors.
@@ -100,9 +102,13 @@
charsets = []
def to_unicode(text):
if not charsets:
- envadapter = IUserPreferredCharsets(request)
- charsets.extend(
- envadapter.getPreferredCharsets() or ['utf-8'])
+ envadapter = IUserPreferredCharsets(request, None)
+ if envadapter:
+ pref = envadapter.getPreferredCharsets()
+ if pref:
+ charsets.extend(pref)
+ if not charsets:
+ charsets.append('utf-8')
for charset in charsets:
try:
return unicode(text, charset)
@@ -164,9 +170,9 @@
priorities = [item['priority'] for item in l]
if len(set(priorities)) != len(l):
raise ConfigurationError('All registered publishers for a given '
- 'method+mimetype must have distinct '
- 'priorities. Please check your ZCML '
- 'configuration')
+ 'scheme+method+mimetype must have '
+ 'distinct priorities. Please check your '
+ 'configuration.')
def get_factories_for(self, scheme, method, mimetype):
if ';' in mimetype:
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/retry.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/retry.py 2009-02-18 03:02:05 UTC (rev 96661)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/retry.py 2009-02-18 04:48:42 UTC (rev 96662)
@@ -20,10 +20,10 @@
from zope.pipeline.autotemp import AutoTemporaryFile
-class Retry(object):
+class RetryApp(object):
"""Retries requests when a Retry or ConflictError propagates.
- This middleware app should enclose the app that creates zope.request.
+ This app should enclose the app that creates zope.request.
It sets an environment variable named 'zope.can_retry'. Error handlers
should propagate Retry or ConflictError when 'zope.can_retry' is
true.
@@ -37,7 +37,9 @@
def __call__(self, environ, start_response):
wsgi_input = environ.get('wsgi.input')
if wsgi_input is not None:
- if not hasattr(wsgi_input, 'seek'):
+ try:
+ wsgi_input.seek(0)
+ except (AttributeError, IOError):
# make the input stream rewindable
f = AutoTemporaryFile()
f.copyfrom(wsgi_input)
@@ -58,8 +60,6 @@
try:
res = self.next_app(environ, retryable_start_response)
except (Retry, ConflictError):
- if 'zope.request' in environ:
- del environ['zope.request']
if wsgi_input is not None:
wsgi_input.seek(0)
attempt += 1
Added: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/requestsetup.txt
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/requestsetup.txt (rev 0)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/requestsetup.txt 2009-02-18 04:48:42 UTC (rev 96662)
@@ -0,0 +1,90 @@
+
+Request Creation
+----------------
+
+The ``CreateRequest`` application puts a Zope-style request object in the
+WSGI environment. Zope-style request objects are mainly used for
+object traversal.
+
+Create an app that prints the contents of the created request.
+
+ >>> attempts = []
+ >>> def my_app(environ, start_response):
+ ... status = '200 OK'
+ ... response_headers = [('Content-type','text/plain')]
+ ... start_response(status, response_headers)
+ ... request = environ['zope.request']
+ ... return [repr(request)]
+
+Now put CreateRequest in front of the test app and try it out.
+
+ >>> from zope.pipeline.apps.requestsetup import CreateRequest
+ >>> app = CreateRequest(my_app)
+ >>> env = {'CONTENT_TYPE': 'text/html; charset=UTF-8'}
+ >>> got_headers = []
+ >>> def start_response(status, headers, exc_info=None):
+ ... got_headers[:] = list(headers)
+ >>> app(env, start_response)
+ Traceback (most recent call last):
+ ...
+ ConfigurationError: No registered request factory found for (http/GET/text/html; charset=UTF-8)
+
+That happened because no request factories have been registered. Add one
+and try again.
+
+ >>> class TestRequest(object):
+ ... def __init__(self, environ):
+ ... self.environ = environ
+ ... def close(self):
+ ... pass
+ >>> from zope.pipeline.apps.requestsetup import factoryRegistry
+ >>> factoryRegistry.register('http', 'GET', '*', 'a', 10, TestRequest)
+ >>> app(env, start_response)
+ ['<TestRequest object at ...>']
+
+Register another type of request and give it a higher priority.
+
+ >>> class HighTestRequest(TestRequest):
+ ... pass
+ >>> factoryRegistry.register('http', 'GET', '*', 'b', 20, HighTestRequest)
+ >>> app(env, start_response)
+ ['<HighTestRequest object at ...>']
+
+Overwrite the registration by reusing a name.
+
+ >>> class HigherTestRequest(TestRequest):
+ ... pass
+ >>> factoryRegistry.register('http', 'GET', '*', 'b', 20, HigherTestRequest)
+ >>> app(env, start_response)
+ ['<HigherTestRequest object at ...>']
+
+Try to register another by a different name but with the same priority.
+
+ >>> class XTestRequest(HighTestRequest):
+ ... pass
+ >>> factoryRegistry.register('http', 'GET', '*', 'c', 20, XTestRequest)
+ Traceback (most recent call last):
+ ...
+ ConfigurationError: All registered publishers for a given scheme+method+mimetype must have distinct priorities. Please check your configuration.
+
+
+Form Parsing
+------------
+
+The ``ParseForm`` application uses ``zope.httpform`` to parse the form
+data in a request.
+
+ >>> from zope.pipeline.apps.requestsetup import ParseForm
+ >>> class FormTestRequest(object):
+ ... def __init__(self, environ):
+ ... self.environ = environ
+ ... def close(self):
+ ... pass
+ ... def __repr__(self):
+ ... return 'FormTestRequest(%s)' % repr(self.form)
+ >>> env = {'QUERY_STRING': 'a:int:list=3&a:int:list=4'}
+ >>> factoryRegistry.__init__()
+ >>> factoryRegistry.register('http', 'GET', '*', 't', 0, FormTestRequest)
+ >>> app = CreateRequest(ParseForm(my_app))
+ >>> app(env, start_response)
+ ["FormTestRequest({u'a': [3, 4]})"]
Added: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/retry.txt
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/retry.txt (rev 0)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/retry.txt 2009-02-18 04:48:42 UTC (rev 96662)
@@ -0,0 +1,106 @@
+
+Retry Application
+-----------------
+
+The retry application catches certain kinds of exceptions that occur
+later in the pipeline and retries them automatically. This is useful
+for any transactional system that sometimes generates conflict errors
+that are resolved easily.
+
+To test the retry app, first we'll make a simple app that generates a
+Retry exception the first time, but not the second time.
+
+ >>> from zope.publisher.interfaces.exceptions import Retry
+ >>> attempts = []
+ >>> def my_app(environ, start_response):
+ ... attempts.append(1)
+ ... status = '200 OK'
+ ... response_headers = [('Content-type','text/plain')]
+ ... response_headers += [('X-Attempts', str(len(attempts)))]
+ ... start_response(status, response_headers)
+ ... if len(attempts) == 1:
+ ... if environ.get('zope.can_retry'):
+ ... raise Retry()
+ ... else:
+ ... raise ValueError()
+ ... return ['Hello world!\n']
+
+Now put RetryApp in front of the test app and try it out.
+
+ >>> from zope.pipeline.apps.retry import RetryApp
+ >>> app = RetryApp(my_app)
+ >>> env = {}
+ >>> got_headers = []
+ >>> def start_response(status, headers, exc_info=None):
+ ... got_headers[:] = list(headers)
+ >>> app(env, start_response)
+ ['Hello world!\n']
+
+The test app was called twice.
+
+ >>> attempts
+ [1, 1]
+ >>> got_headers
+ [('Content-type', 'text/plain'), ('X-Attempts', '2')]
+
+Do it again, but this time only allow 2 attempts.
+
+ >>> del attempts[:]
+ >>> del got_headers[:]
+ >>> app = RetryApp(my_app, max_attempts=2)
+ >>> app(env, start_response)
+ ['Hello world!\n']
+
+The test app was called twice.
+
+ >>> attempts
+ [1, 1]
+ >>> got_headers
+ [('Content-type', 'text/plain'), ('X-Attempts', '2')]
+
+Once more, but this time only allow 1 attempt.
+
+ >>> del attempts[:]
+ >>> del got_headers[:]
+ >>> app = RetryApp(my_app, max_attempts=1)
+ >>> app(env, start_response)
+ Traceback (most recent call last):
+ ...
+ ValueError
+
+
+Repeating the input stream
+--------------------------
+
+Create a test app that echoes a stream back.
+
+ >>> del attempts[:]
+ >>> del got_headers[:]
+ >>> def echo_app(environ, start_response):
+ ... data = environ['wsgi.input'].read()
+ ... attempts.append(1)
+ ... status = '200 Ok'
+ ... response_headers = [('Content-Type', 'text/plain'),
+ ... ('Content-Length', str(len(data)))]
+ ... start_response(status, response_headers)
+ ... if len(attempts) == 1:
+ ... if environ.get('zope.can_retry'):
+ ... raise Retry()
+ ... else:
+ ... raise ValueError()
+ ... return [data]
+ >>> class Stream:
+ ... def __init__(self, data):
+ ... self.pos = 0
+ ... self.data = data
+ ... def read(self, bytes=-1):
+ ... if bytes < 0:
+ ... bytes = len(self.data) - self.pos
+ ... res = self.data[self.pos : self.pos + bytes]
+ ... self.pos += bytes
+ ... return res
+ >>> env = {'wsgi.input': Stream('0123456789' * 4)}
+ >>> app = RetryApp(echo_app)
+ >>> app(env, start_response)
+ ['0123456789012345678901234567890123456789']
+
Added: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/tests.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/tests.py (rev 0)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/tests.py 2009-02-18 04:48:42 UTC (rev 96662)
@@ -0,0 +1,38 @@
+##############################################################################
+#
+# Copyright (c) 2009 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.
+#
+##############################################################################
+"""Tests of this package"""
+
+import unittest
+
+from zope.testing import doctest
+
+flags = doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS
+
+from zope.testing.cleanup import cleanUp
+
+def setUp(test=None):
+ cleanUp()
+
+def tearDown(test=None):
+ cleanUp()
+
+def test_suite():
+ return unittest.TestSuite([
+ doctest.DocFileSuite('retry.txt', optionflags=flags),
+ doctest.DocFileSuite('requestsetup.txt', optionflags=flags,
+ setUp=setUp, tearDown=tearDown),
+ ])
+
+if __name__ == '__main__':
+ unittest.main()
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/configure.zcml
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/configure.zcml 2009-02-18 03:02:05 UTC (rev 96661)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/configure.zcml 2009-02-18 04:48:42 UTC (rev 96662)
@@ -19,7 +19,7 @@
control_transaction
event
handle_error
- process_form
+ parse_form
authenticate
traverse
annotate_transaction
@@ -40,7 +40,7 @@
<wsgi:application
name="retry"
- factory=".apps.retry.Retry"
+ factory=".apps.retry.RetryApp"
for=".interfaces.INoRequest" />
<wsgi:application
@@ -76,14 +76,14 @@
<!-- no form processing for non-browser requests -->
<wsgi:application
- name="process_form"
+ name="parse_form"
factory=".apps.passthrough"
for="zope.publisher.interfaces.IRequest" />
<!-- process forms for browser requests -->
<wsgi:application
- name="process_form"
- factory=".apps.requestsetup.ProcessForm"
+ name="parse_form"
+ factory=".apps.requestsetup.ParseForm"
for="zope.publisher.interfaces.browser.IBrowserRequest" />
<!--wsgi:application
More information about the Checkins
mailing list