[Checkins] SVN: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/ Moved variables intended for communication within the pipeline from the request to the
Shane Hathaway
shane at hathawaymix.org
Sat Feb 21 21:30:39 EST 2009
Log message for revision 96971:
Moved variables intended for communication within the pipeline from the request to the
WSGI environment. This should make it easier to use more types of request objects.
Changed:
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/authenticate.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/event.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/fixrel.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/mapply.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/openroot.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/requestsetup.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/retry.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/switch.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/mapply.txt
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/openroot.txt
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/requestsetup.txt
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/retry.txt
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/tests.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/txnctl.txt
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/traversal.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/txnctl.py
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/configure.zcml
U Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/entry.py
A Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/envkeys.py
-=-
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/authenticate.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/authenticate.py 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/authenticate.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -12,32 +12,38 @@
#
##############################################################################
+from zope.app.security.interfaces import IAuthentication
+from zope.app.security.interfaces import IFallbackUnauthenticatedPrincipal
from zope.component import getGlobalSiteManager
from zope.interface import implements
from zope.publisher.interfaces import IWSGIApplication
+from zope.security.management import endInteraction
from zope.security.management import newInteraction
-from zope.security.management import endInteraction
-from zope.app.security.interfaces import IAuthentication
-from zope.app.security.interfaces import IFallbackUnauthenticatedPrincipal
+from zope.pipeline.envkeys import REQUEST_KEY
+from zope.pipeline.envkeys import TRAVERSAL_HOOKS_KEY
class Authenticator(object):
"""WSGI app that hooks into Zope-based authentication.
- The WSGI environment must contain 'zope.request'.
+ The WSGI environment must contain 'zope.pipeline.request'.
+ Adds a traversal hook if local authentication is enabled.
"""
implements(IWSGIApplication)
- def __init__(self, next_app):
+ def __init__(self, next_app, local_auth=True):
self.next_app = next_app
+ self.local_auth = local_auth
def __call__(self, environ, start_response):
- request = environ['zope.request']
+ request = environ[REQUEST_KEY]
auth = getGlobalSiteManager().getUtility(IAuthentication)
principal = auth.authenticate(request)
if principal is None:
- request.traversal_hooks.append(placeful_auth)
+ if self.local_auth:
+ hooks = environ.setdefault(TRAVERSAL_HOOKS_KEY, [])
+ hooks.append(placeful_auth)
principal = auth.unauthenticatedPrincipal()
if principal is None:
# Get the fallback unauthenticated principal
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/event.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/event.py 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/event.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -18,12 +18,16 @@
from zope.publisher.interfaces.event import BeforeTraverseEvent
from zope.publisher.interfaces.event import EndRequestEvent
+from zope.pipeline.envkeys import TRAVERSAL_HOOKS_KEY
+from zope.pipeline.envkeys import TRAVERSED_KEY
+
class EventNotifier(object):
"""Fires request-related events.
- Fires BeforeTraverseEvent and EndRequestEvent at the appropriate
- times.
+ Adds a traversal hook to the environment. Fires BeforeTraverseEvent
+ and EndRequestEvent at the appropriate times. Requires the
+ 'zope.pipeline.traversed' key for firing events.
"""
implements(IWSGIApplication)
@@ -31,16 +35,17 @@
self.next_app = next_app
def __call__(self, environ, start_response):
- request = environ['zope.request']
- request.traversal_hooks.append(fireBeforeTraverse)
+ hooks = environ.setdefault(TRAVERSAL_HOOKS_KEY, [])
+ hooks.append(fire_before_traverse)
try:
return self.next_app(environ, start_response)
finally:
- if request.traversed:
- name, ob = request.traversed[-1]
+ traversed = environ.get(TRAVERSED_KEY)
+ if traversed:
+ name, ob = traversed[-1]
else:
ob = None
notify(EndRequestEvent(ob, request))
-def fireBeforeTraverse(request, ob):
+def fire_before_traverse(request, ob):
notify(BeforeTraverseEvent(ob, request))
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/fixrel.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/fixrel.py 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/fixrel.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -12,7 +12,13 @@
#
##############################################################################
+from zope.interface import implements
+from zope.pipeline.envkeys import IMPLICIT_TRAVERSAL_STEPS_KEY
+from zope.pipeline.envkeys import REQUEST_KEY
+from zope.pipeline.envkeys import STRING_RESULT_HOOKS_KEY
+from zope.publisher.interfaces import IWSGIApplication
+
class FixRelativeLinks(object):
"""WSGI middleware that fixes relative links.
@@ -30,7 +36,7 @@
self.next_app = next_app
def __call__(self, environ, start_response):
- request = environ['zope.request']
+ request = environ[REQUEST_KEY]
need_fix = False
allow_redirect = False
@@ -39,33 +45,42 @@
# When there is a :method or :action form variable, we need to
# fix relative URLs, but not by redirecting.
need_fix = True
+
else:
- # If the URL ends with a slash, the URL specified one default
- # traversal step, otherwise the URL specified zero.
- # Compare the number of default traversal steps
- # specified by the URL with the number of default traversal
- # steps actually performed. Set need_fix to True if
- # the specified number does not match the actual.
+ actual_steps = environ[IMPLICIT_TRAVERSAL_STEPS_KEY]
+
+ # If the URL ends with a slash, the URL specified one
+ # default traversal step, otherwise the URL specified zero.
if environ['PATH_INFO'].endswith('/'):
- specified = 1
+ specified_steps = 1
else:
- specified = 0
- actual = request.traversed_default
- if actual != specified:
+ specified_steps = 0
+
+ # Compare the number of default traversal steps specified
+ # by the URL with the number of default traversal steps
+ # actually performed. Set need_fix to True if the specified
+ # number does not match the actual.
+ if actual_steps != specified_steps:
need_fix = True
allow_redirect = (
self.allow_redirect and request.method == 'GET')
- if not need_fix:
- # No fix required
- return self.next_app(environ, start_response)
+ if need_fix:
+ if redirect:
+ # Redirect, then end the pipeline early
+ response = request.response
+ response.redirect(request.getURL())
+ start_response(
+ response.getStatusString(), response.getHeaders())
+ return response.consumeBodyIter()
- if redirect:
- # Redirect, then end the pipeline early
- request.response.redirect(request.getURL())
- start_response(response.getStatusString(), response.getHeaders())
- return response.consumeBodyIter()
+ # add a hook for altering string output.
+ hooks = environ.setdefault(STRING_RESULT_HOOKS_KEY, [])
+ hooks.append(self.string_result_hook)
- # TODO: Call the app. Buffer and alter the response
- # if the result is HTML.
+ return self.next_app(environ, start_response)
+ def string_result_hook(self, result, environ):
+ # TODO: if result is HTML, add base tag and adjust the response
+ # content-length.
+ return result
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/mapply.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/mapply.py 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/mapply.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -16,19 +16,24 @@
from zope.proxy import removeAllProxies
from zope.publisher.interfaces import IWSGIApplication
+from zope.pipeline.envkeys import REQUEST_KEY
+from zope.pipeline.envkeys import TRAVERSED_KEY
+
class Caller(object):
"""WSGI app that calls the traversed object.
- Requires 'zope.request', which implements IRequest, in the environment.
- The 'traversed' attribute of the request must be set.
+ Requires 'zope.pipeline.request' and 'zope.pipeline.traversed' in
+ the environment.
"""
implements(IWSGIApplication)
def __call__(self, environ, start_response):
- request = environ['zope.request']
- name, ob = request.traversed[-1]
- result = mapply(ob, (), request)
+ traversed = environ[TRAVERSED_KEY]
+ request = environ[REQUEST_KEY]
+ positional = request.getPositionalArguments()
+ name, ob = traversed[-1]
+ result = mapply(ob, positional, request)
response = request.response
if result is not response:
response.setResult(result)
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/openroot.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/openroot.py 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/openroot.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -12,54 +12,65 @@
#
##############################################################################
+import re
+
from zope.component import getUtility
from zope.interface import implements
from zope.interface import Interface
from zope.publisher.interfaces import IWSGIApplication
from zope.security.checker import ProxyFactory
+from zope.pipeline.envkeys import REQUEST_KEY
+from zope.pipeline.envkeys import TRAVERSED_KEY
+
class RootOpener(object):
- """Puts a root object in 'zope.request' of the WSGI environment.
+ """Establishes the traversal root in the WSGI environment.
- Requires the environment to contain 'zope.database',
- which is normally a ZODB.DB.DB object.
- Sets request.traversed to a list with one element.
+ Opens the database, finds the Zope application root, puts a
+ security proxy on the root object, and sets
+ 'zope.pipeline.traversed' in the environment to a list with one
+ element containing (root_name, root). If the environment contains
+ 'zope.pipeline.request', an annotation is added to the request.
Also closes the database connection on the way out.
- Special case: if the traversal stack contains "++etc++process",
- instead of opening the database, this uses the utility by that
- name as the root object.
+ Special case: if the PATH_INFO contains "++etc++process", instead
+ of opening the database, this uses the utility by that name as the
+ root object.
"""
implements(IWSGIApplication)
root_name = 'Application'
app_controller_name = '++etc++process'
+ use_app_controller_re = re.compile('/[+][+]etc[+][+]process(/|$)')
def __init__(self, next_app, database):
self.next_app = next_app
self.database = database
+ self.proxy_factory = ProxyFactory
def __call__(self, environ, start_response):
- request = environ['zope.request']
-
- # If the traversal stack contains self.app_controller_name,
- # then we should get the app controller rather than look
- # in the database.
- if self.app_controller_name in request.traversal_stack:
+ # If the PATH_INFO contains the app controller name,
+ # then we should use the app controller rather than open
+ # the database.
+ path = environ.get('PATH_INFO', '')
+ if self.use_app_controller_re.search(path) is not None:
root = getUtility(Interface, name=self.app_controller_name)
- request.traversed = [(self.app_controller_name, root)]
+ environ[TRAVERSED_KEY] = [(self.app_controller_name, root)]
return self.next_app(environ, start_response)
# Open the database.
conn = self.database.open()
try:
- request.annotations['ZODB.interfaces.IConnection'] = conn
+ request = environ.get(REQUEST_KEY)
+ if request is not None:
+ request.annotations['ZODB.interfaces.IConnection'] = conn
root = conn.root()
app = root.get(self.root_name, None)
if app is None:
raise SystemError("Zope Application Not Found")
- request.traversed = [(self.root_name, ProxyFactory(app))]
+ environ[TRAVERSED_KEY] = [
+ (self.root_name, self.proxy_factory(app))]
return self.next_app(environ, start_response)
finally:
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-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/requestsetup.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -24,6 +24,7 @@
from zope.publisher.interfaces import IWSGIApplication
from zope.testing import cleanup
+from zope.pipeline.envkeys import REQUEST_KEY
from zope.pipeline.interfaces import IRequestFactoryRegistry
@@ -46,7 +47,7 @@
mimetype = environ.get('CONTENT_TYPE', '')
request = factoryRegistry.make_request(
scheme, method, mimetype, environ)
- environ['zope.request'] = request
+ environ[REQUEST_KEY] = request
self.set_locale(request)
self.set_skin(request)
@@ -55,7 +56,7 @@
return self.next_app(environ, start_response)
finally:
request.close()
- del environ['zope.request']
+ del environ[REQUEST_KEY]
def set_locale(self, request):
envadapter = IUserPreferredLanguages(request, None)
@@ -88,7 +89,7 @@
This step is separate from request creation so that the
error handling step can catch form data errors.
- Requires the environment to contain a 'zope.request' that
+ Requires the environment to contain a 'zope.pipeline.request' that
is an IHTTPRequest, not just an IRequest.
"""
implements(IWSGIApplication)
@@ -97,7 +98,7 @@
self.next_app = next_app
def __call__(self, environ, start_response):
- request = environ['zope.request']
+ request = environ[REQUEST_KEY]
charsets = []
def to_unicode(text):
@@ -115,8 +116,8 @@
except UnicodeError:
pass
raise UnicodeError(
- "Unable to decode %s using any available character set"
- % repr(text))
+ "Unable to decode %s using any of the character sets: %s"
+ % (repr(text), repr(charsets)))
parser = FormParser(environ, to_unicode=to_unicode)
request.form = parser.parse()
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-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/retry.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -12,21 +12,23 @@
#
##############################################################################
+from ZODB.POSException import ConflictError
from zope.interface import implements
+from zope.publisher.interfaces.exceptions import Retry
from zope.publisher.interfaces import IWSGIApplication
-from zope.publisher.interfaces.exceptions import Retry
-from ZODB.POSException import ConflictError
from zope.pipeline.autotemp import AutoTemporaryFile
+from zope.pipeline.envkeys import CAN_RETRY_KEY
+from zope.pipeline.envkeys import RESETTABLE_KEYS
class RetryApp(object):
"""Retries requests when a Retry or ConflictError propagates.
- 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.
+ This app should enclose the app that creates zope.pipeline.request.
+ It sets an environment variable named 'zope.pipeline.can_retry'.
+ The 'handle_error' app should propagate Retry or ConflictError
+ when 'zope.pipeline.can_retry' is true.
"""
implements(IWSGIApplication)
@@ -56,12 +58,13 @@
while attempt < self.max_attempts:
start_response_params = []
output_file = []
- environ['zope.can_retry'] = True
+ environ[CAN_RETRY_KEY] = True
try:
res = self.next_app(environ, retryable_start_response)
except (Retry, ConflictError):
if wsgi_input is not None:
wsgi_input.seek(0)
+ self.reset_environ(environ)
attempt += 1
else:
if start_response_params:
@@ -73,5 +76,10 @@
return res
# try once more, this time without retry support
- environ['zope.can_retry'] = False
+ environ[CAN_RETRY_KEY] = False
return self.next_app(environ, start_response)
+
+ def reset_environ(self, environ):
+ for key in RESETTABLE_KEYS:
+ if key in environ:
+ del environ[key]
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/switch.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/switch.py 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/switch.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -18,6 +18,7 @@
from zope.publisher.interfaces import IWSGIApplication
from zope.pipeline.entry import create_pipeline
+from zope.pipeline.envkeys import REQUEST_KEY
from zope.pipeline.interfaces import IPipelineParticipant
@@ -25,7 +26,7 @@
"""WSGI application that switches to a pipeline based on the request type.
This should be placed at the end of the INoRequest pipeline.
- Requires 'zope.request' in the environment.
+ Requires 'zope.pipeline.request' in the environment.
"""
implements(IWSGIApplication, IPipelineParticipant)
@@ -37,7 +38,7 @@
self.pipeline_params = pipeline_params
def __call__(self, environ, start_response):
- request = environ['zope.request']
+ request = environ[REQUEST_KEY]
provided = tuple(providedBy(request))
pipeline = self._cache.get(provided)
if pipeline is None:
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/mapply.txt
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/mapply.txt 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/mapply.txt 2009-02-22 02:30:39 UTC (rev 96971)
@@ -6,6 +6,8 @@
and sets the response according to what the function returned.
>>> class TestRequest(object):
+ ... def getPositionalArguments(self):
+ ... return ()
... def get(self, key, default=None):
... if key == 'a':
... return 6
@@ -24,20 +26,22 @@
... return [self.data]
>>> def compute(a, b, c=4):
... return '%d%d%d' % (a, b, c)
- >>> r = TestRequest()
- >>> r.traversed = [('obj', compute)]
- >>> r.response = TestResponse()
+ >>> request = TestRequest()
+ >>> request.response = TestResponse()
>>> from zope.pipeline.apps.mapply import Caller
>>> app = Caller()
>>> app
Caller()
- >>> env = {'zope.request': r}
+ >>> environ = {
+ ... 'zope.pipeline.request': request,
+ ... 'zope.pipeline.traversed': [('obj', compute)]
+ ... }
>>> got_headers = []
>>> got_status = []
>>> def start_response(status, headers, exc_info=None):
... got_status[:] = [status]
... got_headers[:] = list(headers)
- >>> app(env, start_response)
+ >>> app(environ, start_response)
['684']
>>> got_status
['200 Ok']
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/openroot.txt
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/openroot.txt 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/openroot.txt 2009-02-22 02:30:39 UTC (rev 96971)
@@ -25,19 +25,20 @@
... status = '200 OK'
... response_headers = [('Content-type','text/plain')]
... start_response(status, response_headers)
- ... request = environ['zope.request']
- ... return [repr(request.traversed)]
+ ... request = environ['zope.pipeline.request']
+ ... traversed = environ['zope.pipeline.traversed']
+ ... return [repr(traversed)]
>>> app = RootOpener(my_app, db)
Call the app.
>>> class TestRequest(object):
... def __init__(self):
- ... self.traversal_stack = []
- ... self.traversed = []
... self.annotations = {}
>>> request = TestRequest()
- >>> environ = {'zope.request': request}
+ >>> environ = {
+ ... 'zope.pipeline.request': request,
+ ... }
>>> def start_response(status, headers, exc_info=None):
... pass
>>> app(environ, start_response)
@@ -71,7 +72,9 @@
... pass
>>> provideUtility(ProcessRoot, name='++etc++process', provides=Interface)
>>> request = TestRequest()
- >>> request.traversal_stack.append('++etc++process')
- >>> environ = {'zope.request': request}
+ >>> environ = {
+ ... 'zope.pipeline.request': request,
+ ... 'PATH_INFO': '/++etc++process/',
+ ... }
>>> app(environ, start_response)
["[('++etc++process', <class 'ProcessRoot'>)]"]
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/requestsetup.txt
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/requestsetup.txt 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/requestsetup.txt 2009-02-22 02:30:39 UTC (rev 96971)
@@ -13,7 +13,7 @@
... status = '200 OK'
... response_headers = [('Content-type','text/plain')]
... start_response(status, response_headers)
- ... request = environ['zope.request']
+ ... request = environ['zope.pipeline.request']
... return [repr(request)]
Now put CreateRequest in front of the test app and try it out.
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/retry.txt
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/retry.txt 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/retry.txt 2009-02-22 02:30:39 UTC (rev 96971)
@@ -19,7 +19,7 @@
... response_headers += [('X-Attempts', str(len(attempts)))]
... start_response(status, response_headers)
... if len(attempts) == 1:
- ... if environ.get('zope.can_retry'):
+ ... if environ.get('zope.pipeline.can_retry'):
... raise Retry()
... else:
... raise ValueError()
@@ -84,7 +84,7 @@
... ('Content-Length', str(len(data)))]
... start_response(status, response_headers)
... if len(attempts) == 1:
- ... if environ.get('zope.can_retry'):
+ ... if environ.get('zope.pipeline.can_retry'):
... raise Retry()
... else:
... raise ValueError()
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/tests.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/tests.py 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/tests.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -16,9 +16,6 @@
import unittest
from zope.testing import doctest
-
-flags = doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS
-
from zope.testing.cleanup import cleanUp
def setUp(test=None):
@@ -28,16 +25,19 @@
cleanUp()
def test_suite():
- return unittest.TestSuite([
- doctest.DocFileSuite('mapply.txt', optionflags=flags),
- doctest.DocFileSuite('openroot.txt', optionflags=flags,
- setUp=setUp, tearDown=tearDown),
- doctest.DocFileSuite('requestsetup.txt', optionflags=flags,
- setUp=setUp, tearDown=tearDown),
- doctest.DocFileSuite('retry.txt', optionflags=flags),
- doctest.DocFileSuite('txnctl.txt', optionflags=flags,
- setUp=setUp, tearDown=tearDown),
- ])
+ suites = []
+ for name in [
+ 'mapply.txt',
+ 'openroot.txt',
+ 'requestsetup.txt',
+ 'retry.txt',
+ 'txnctl.txt',
+ ]:
+ suites.append(doctest.DocFileSuite(
+ name,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ setUp=setUp, tearDown=tearDown))
+ return unittest.TestSuite(suites)
if __name__ == '__main__':
unittest.main()
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/txnctl.txt
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/txnctl.txt 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/tests/txnctl.txt 2009-02-22 02:30:39 UTC (rev 96971)
@@ -87,7 +87,7 @@
--------------------------
The TransactionAnnotator application adds information to the
-zope.request after the rest of the pipeline has finished.
+zope.pipeline.request after the rest of the pipeline has finished.
Set up enough of a request to make the TransactionAnnotator work.
@@ -104,7 +104,6 @@
>>> class TestRequest(object):
... pass
>>> request = TestRequest()
- >>> request.traversed = [('xyz', TestRoot().mymethod)]
>>> class TestPrincipal(object):
... pass
>>> request.principal = TestPrincipal()
@@ -116,7 +115,10 @@
... return ['done annotating']
>>> from zope.pipeline.apps.txnctl import TransactionAnnotator
>>> app = TransactionAnnotator(my_app)
- >>> environ = {'zope.request': request}
+ >>> environ = {
+ ... 'zope.pipeline.request': request,
+ ... 'zope.pipeline.traversed': [('xyz', TestRoot().mymethod)],
+ ... }
>>> app(environ, None)
['done annotating']
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/traversal.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/traversal.py 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/traversal.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -15,11 +15,16 @@
from zope.interface import implements
from zope.publisher.interfaces import IWSGIApplication
+from zope.pipeline.envkeys import TRAVERSAL_HOOKS_KEY
+from zope.pipeline.envkeys import TRAVERSED_KEY
+
class Traverser(object):
"""Traverses the object graph based on the traversal stack.
- Requires 'zope.request' in the WSGI environment.
+ Requires 'zope.pipeline.traversed' in the WSGI environment. Calls
+ the hooks listed in 'zope.pipeline.traversal_hooks', if there are
+ any.
"""
implements(IWSGIApplication)
@@ -27,38 +32,54 @@
self.next_app = next_app
def __call__(self, environ, start_response):
- request = environ['zope.request']
- self.traverse(request)
+ self.traverse(environ)
return self.next_app(environ, start_response)
- def traverse(self, request):
- traversal_stack = request.traversal_stack
- traversal_hooks = request.traversal_hooks
- traversed = request.traversed
+ def get_stack(self, environ):
+ pass
+ def traverse(self, environ):
+ stack = self.get_stack(environ)
+ traversed = environ[TRAVERSED_KEY]
+ hooks = environ.get(TRAVERSAL_HOOKS_KEY, ())
+
root_name, obj = traversed[-1]
prev_object = None
while True:
if obj is not prev_object:
# Call hooks (but not more than once).
- for hook in traversal_hooks:
+ for hook in hooks:
hook(request, obj)
+ prev_object = obj
- if not traversal_stack:
- break
+ if not stack:
+ obj, stack = self.add_steps(environ)
+ if stack:
+ # call traversal hooks and traverse some more
+ continue
+ else:
+ # done traversing
+ break
- prev_object = obj
-
- # Traverse to the next step.
- name = traversal_stack.pop()
+ # Traverse the next step.
+ name = stack.pop()
obj = self.traverse_name(obj, name)
traversed.append((name, obj))
def traverse_name(self, obj, name):
pass
+ def add_steps(self, environ):
+ pass
+
class HTTPTraverser(Traverser):
+ # includes form_action (or should this be in BrowserTraverser?)
implements(IWSGIApplication)
+
+class BrowserTraverser(HTTPTraverser):
+ # includes browserDefault traversal
+ implements(IWSGIApplication)
+
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/txnctl.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/txnctl.py 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/apps/txnctl.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -14,17 +14,20 @@
from new import instancemethod
-import transaction
-from zope.location.interfaces import ILocationInfo
from zope.interface import implements
from zope.interface import providedBy
+from zope.location.interfaces import ILocationInfo
from zope.publisher.interfaces import IRequest
from zope.publisher.interfaces import IWSGIApplication
from zope.security.proxy import removeSecurityProxy
+import transaction
+from zope.pipeline.envkeys import REQUEST_KEY
+from zope.pipeline.envkeys import TRAVERSED_KEY
+
class TransactionController(object):
- """WSGI middleware that begins and commits/aborts transactions.
+ """WSGI application that begins and commits/aborts transactions.
"""
implements(IWSGIApplication)
@@ -47,9 +50,10 @@
class TransactionAnnotator(object):
- """WSGI middleware that annotates transactions.
+ """WSGI application that annotates transactions.
- Requires 'zope.request' in the environment.
+ Requires 'zope.pipeline.request' and 'zope.pipeline.traversed'
+ in the environment.
"""
implements(IWSGIApplication)
@@ -60,8 +64,8 @@
res = self.next_app(environ, start_response)
txn = transaction.get()
if not txn.isDoomed():
- request = environ['zope.request']
- name, ob = request.traversed[-1]
+ request = environ[REQUEST_KEY]
+ name, ob = environ[TRAVERSED_KEY][-1]
self.annotate(txn, request, ob)
return res
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/configure.zcml
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/configure.zcml 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/configure.zcml 2009-02-22 02:30:39 UTC (rev 96971)
@@ -17,9 +17,10 @@
log
open_root
control_transaction
+ set_site
event
handle_error
- parse_form
+ process_input
authenticate
traverse
annotate_transaction
@@ -74,15 +75,15 @@
name="handle_error"
factory="..." /-->
-<!-- no form processing for non-browser requests -->
+<!-- no input processing for non-browser requests -->
<wsgi:application
- name="parse_form"
+ name="process_input"
factory=".apps.passthrough"
for="zope.publisher.interfaces.IRequest" />
-<!-- process forms for browser requests -->
+<!-- parse forms for browser requests -->
<wsgi:application
- name="parse_form"
+ name="process_input"
factory=".apps.requestsetup.ParseForm"
for="zope.publisher.interfaces.browser.IBrowserRequest" />
Modified: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/entry.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/entry.py 2009-02-21 22:33:34 UTC (rev 96970)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/entry.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -24,14 +24,13 @@
def create_pipeline(params, request_provides=None):
"""Return a pipeline as a WSGI application.
- The `params` contains a mapping of application name to
- factory keyword parameter map. An example `params` would be
- ``{'open_root': {'database': zodb_db_object}}``.
+ The `params` contains a mapping of application name to factory
+ keyword parameter map. An example `params` would be ``{'open_root':
+ {'database': zodb_db_object}}``.
- The `request_provides` parameter varies the pipeline according
- to the type of the `zope.request` in the WSGI environment.
- If the WSGI environment to process has no `zope.request`, the
- `request.provides` parameter should be None (the default).
+ The `request_provides` parameter varies the pipeline according to
+ the interfaces provided by a request object. If `request_provides`
+ is not specified, the pipeline for INoRequest is created.
"""
if request_provides is None:
request_provides = (INoRequest,)
Added: Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/envkeys.py
===================================================================
--- Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/envkeys.py (rev 0)
+++ Sandbox/shane/republish/zope.pipeline/src/zope/pipeline/envkeys.py 2009-02-22 02:30:39 UTC (rev 96971)
@@ -0,0 +1,79 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""WSGI environment key names used by zope.pipeline applications.
+
+This module contains an alias for each of the environment keys below.
+
+'zope.pipeline.can_retry'
+ Contains a boolean value specifying whether the error handling
+ application should propagate Retry and ConflictError exceptions.
+ It is created by the `retry` application and is later read by the
+ `handle_error` application.
+
+'zope.pipeline.request'
+ Contains an object that provides the IRequest interface. The
+ request is created and later closed by the `create_request`
+ application. It is used by several applications, the
+ `switch_pipeline` and `call` applications in particular.
+
+'zope.pipeline.traversal_hooks'
+ Contains a list of functions to call before each traversal step.
+ The `set_site`, `event`, and `authenticate` applications add to
+ this list and the `traverse` application calls the hooks. The
+ `retry` application clears this list.
+
+ Each hook will be called with two parameters, request and ob. The
+ hook function does not need to return anything.
+
+ These hooks will be called before traversing an object for the
+ first time. If the same object is traversed more than once, the
+ hook will still only be called the first time.
+
+'zope.pipeline.traversed'
+ List of (name, obj) steps that have been traversed. Created by the
+ `open_root` application, which adds the application root as the
+ first element of this list. Used by at least the `traverse` and
+ `call` applications.
+
+'zope.pipeline.implicit_traversal_steps'
+ Number of steps that were traversed implicitly. Implicit traversal
+ adds automatic traversal steps to the end of the path; this is
+ primarily used for default views of folders. Created by the
+ `traverse` application and used by the `fix_relative_links`
+ application.
+
+'zope.pipeline.string_result_hooks'
+ A list of functions to call if the response is set to a string
+ result. Each function is passed two parameters, string_result and
+ environ, and returns a new string result. The hooks are processed
+ in order. The `fix_relative_links` application adds this hook and
+ the response object attached to the request uses these hook. The
+ `retry` application removes this hook.
+
+ The hooks are not called if the response is set to something
+ other than a string.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+CAN_RETRY_KEY = "zope.pipeline.can_retry"
+REQUEST_KEY = "zope.pipeline.request"
+TRAVERSAL_HOOKS_KEY = "zope.pipeline.traversal_hooks"
+TRAVERSED_KEY = "zope.pipeline.traversed"
+IMPLICIT_TRAVERSAL_STEPS_KEY = "zope.pipeline.implicit_traversal_steps"
+STRING_RESULT_HOOKS_KEY = "zope.pipeline.string_result_hooks"
+
+RESETTABLE_KEYS = [
+ name for (varname, name) in globals().items() if varname.endswith('_KEY')]
More information about the Checkins
mailing list