[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