[Checkins] SVN: Sandbox/shane/republish/zope/pipeline/ Moved to a two-step application creation process.

Shane Hathaway shane at hathawaymix.org
Sat Feb 14 03:50:38 EST 2009


Log message for revision 96519:
  Moved to a two-step application creation process.
  All sorts of goodness suddenly fell out:
  
  - There is an easy way to pass app construction parameters.
  
  - Multi-adaptation is no longer needed.
  
  - The distinction between the wsgi:application and wsgi:middleware is
    no longer useful, so there is now just wsgi:application.
  
  - The need for a module level pipeline cache disappeared.  Pipeline
    caching is now done in an instance of the SwitchPipeline application.
  
  - There is no longer a risk of bugs caused by applications not
    providing the IWSGIApplication interface, since the pipeline
    builder is no longer aware of IWSGIApplication.
  
  Also renamed self.app to self.next_app in most of the middleware apps.
  
  

Changed:
  U   Sandbox/shane/republish/zope/pipeline/apps/authenticate.py
  U   Sandbox/shane/republish/zope/pipeline/apps/event.py
  U   Sandbox/shane/republish/zope/pipeline/apps/fixrel.py
  U   Sandbox/shane/republish/zope/pipeline/apps/openroot.py
  U   Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py
  U   Sandbox/shane/republish/zope/pipeline/apps/retry.py
  U   Sandbox/shane/republish/zope/pipeline/apps/switch.py
  U   Sandbox/shane/republish/zope/pipeline/apps/traversal.py
  U   Sandbox/shane/republish/zope/pipeline/apps/txnctl.py
  U   Sandbox/shane/republish/zope/pipeline/configure.zcml
  U   Sandbox/shane/republish/zope/pipeline/entry.py
  U   Sandbox/shane/republish/zope/pipeline/interfaces.py
  U   Sandbox/shane/republish/zope/pipeline/meta.zcml
  U   Sandbox/shane/republish/zope/pipeline/zcml.py

-=-
Modified: Sandbox/shane/republish/zope/pipeline/apps/authenticate.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/authenticate.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/apps/authenticate.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -13,7 +13,6 @@
 ##############################################################################
 
 from zope.component import getGlobalSiteManager
-from zope.interface import adapts
 from zope.interface import implements
 from zope.publisher.interfaces import IWSGIApplication
 from zope.security.management import newInteraction
@@ -29,10 +28,9 @@
     The WSGI environment must contain 'zope.request'.
     """
     implements(IWSGIApplication)
-    adapts(IWSGIApplication, IRequest)
 
-    def __init__(self, app, marker_request=None):
-        self.app = app
+    def __init__(self, next_app):
+        self.next_app = next_app
 
     def __call__(self, environ, start_response):
         request = environ['zope.request']
@@ -48,12 +46,12 @@
 
         newInteraction(request)
         try:
-            return self.app(environ, start_response)
+            return self.next_app(environ, start_response)
         finally:
             endInteraction()
 
     def __repr__(self):
-        return '%s(%s)' % (self.__class__.__name__, repr(self.app))
+        return '%s(%s)' % (self.__class__.__name__, repr(self.next_app))
 
 
 def placeful_auth(request, ob):

Modified: Sandbox/shane/republish/zope/pipeline/apps/event.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/event.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/apps/event.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -13,7 +13,6 @@
 ##############################################################################
 
 from zope.event import notify
-from zope.interface import adapts
 from zope.interface import implements
 from zope.publisher.interfaces import IWSGIApplication
 from zope.publisher.interfaces.event import BeforeTraverseEvent
@@ -27,16 +26,15 @@
     times.
     """
     implements(IWSGIApplication)
-    adapts(IWSGIApplication)
 
-    def __init__(self, app):
-        self.app = app
+    def __init__(self, next_app):
+        self.next_app = next_app
 
     def __call__(self, environ, start_response):
         request = environ['zope.request']
         request.traversal_hooks.append(fireBeforeTraverse)
         try:
-            return self.app(environ, start_response)
+            return self.next_app(environ, start_response)
         finally:
             if request.traversed:
                 name, ob = request.traversed[-1]

Modified: Sandbox/shane/republish/zope/pipeline/apps/fixrel.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/fixrel.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/apps/fixrel.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -23,12 +23,11 @@
     to the response text (after calling the app).
     """
     implements(IWSGIApplication)
-    adapts(IWSGIApplication)
 
     allow_redirect = True
 
-    def __init__(self, app):
-        self.app = app
+    def __init__(self, next_app):
+        self.next_app = next_app
 
     def __call__(self, environ, start_response):
         request = environ['zope.request']
@@ -59,7 +58,7 @@
 
         if not need_fix:
             # No fix required
-            return self.app(environ, start_response)
+            return self.next_app(environ, start_response)
 
         if redirect:
             # Redirect, then end the pipeline early

Modified: Sandbox/shane/republish/zope/pipeline/apps/openroot.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/openroot.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/apps/openroot.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -13,7 +13,6 @@
 ##############################################################################
 
 from zope.component import getUtility
-from zope.interface import adapts
 from zope.interface import implements
 from zope.publisher.interfaces import IWSGIApplication
 from zope.security.checker import ProxyFactory
@@ -32,13 +31,13 @@
     name as the root object.
     """
     implements(IWSGIApplication)
-    adapts(IWSGIApplication)
 
     root_name = 'Application'
     app_controller_name = '++etc++process'
 
-    def __init__(self, app):
-        self.app = app
+    def __init__(self, next_app, database):
+        self.next_app = next_app
+        self.database = database
 
     def __call__(self, environ, start_response):
         request = environ['zope.request']
@@ -52,18 +51,15 @@
             return self.app(environ, start_response)
 
         # Open the database.
-        db = environ['zope.database']
-        conn = db.open()
-
-        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))]
-
+        conn = self.database.open()
         try:
-            return self.app(environ, start_response)
+            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))]
+
+            return self.next_app(environ, start_response)
         finally:
             conn.close()

Modified: Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -18,7 +18,6 @@
 from zope.configuration.exceptions import ConfigurationError
 from zope.httpform import FormParser
 from zope.i18n.interfaces import IUserPreferredCharsets
-from zope.interface import adapts
 from zope.interface import implements
 from zope.publisher.interfaces import IWSGIApplication
 from zope.testing import cleanup
@@ -35,10 +34,9 @@
     Also sets the request locale and skin.
     """
     implements(IWSGIApplication)
-    adapts(IWSGIApplication)
 
-    def __init__(self, app):
-        self.app = app
+    def __init__(self, next_app):
+        self.next_app = next_app
 
     def __call__(self, environ, start_response):
         scheme = environ.get('wsgi.url_scheme', 'http').lower()
@@ -51,7 +49,7 @@
         self.set_locale(request)
         self.set_skin(request)
 
-        return self.app(environ, start_response)
+        return self.next_app(environ, start_response)
 
     def set_locale(self, request):
         envadapter = IUserPreferredLanguages(request, None)
@@ -88,11 +86,9 @@
     is an IHTTPRequest, not just an IRequest.
     """
     implements(IWSGIApplication)
-    adapts(IWSGIApplication)
-    request_type = IHTTPRequest
 
-    def __init__(self, app):
-        self.app = app
+    def __init__(self, next_app):
+        self.next_app = next_app
 
     def __call__(self, environ, start_response):
         request = environ['zope.request']
@@ -117,7 +113,7 @@
         if parser.action:
             request.traversal_stack.insert(0, parser.action)
 
-        return self.app(environ, start_response)
+        return self.next_app(environ, start_response)
 
 
 class RequestFactoryRegistry(object):

Modified: Sandbox/shane/republish/zope/pipeline/apps/retry.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/retry.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/apps/retry.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -12,7 +12,6 @@
 #
 ##############################################################################
 
-from zope.interface import adapts
 from zope.interface import implements
 from zope.publisher.interfaces import IWSGIApplication
 from zope.publisher.interfaces.exceptions import Retry
@@ -30,10 +29,9 @@
     true.
     """
     implements(IWSGIApplication)
-    adapts(IWSGIApplication)
 
-    def __init__(self, app, max_attempts=3):
-        self.app = app
+    def __init__(self, next_app, max_attempts=3):
+        self.next_app = next_app
         self.max_attempts = max_attempts
 
     def __call__(self, environ, start_response):
@@ -58,7 +56,7 @@
             output_file = []
             environ['zope.can_retry'] = True
             try:
-                res = self.app(environ, retryable_start_response)
+                res = self.next_app(environ, retryable_start_response)
             except (Retry, ConflictError):
                 if 'zope.request' in environ:
                     del environ['zope.request']
@@ -76,4 +74,4 @@
 
         # try once more, this time without retry support
         environ['zope.can_retry'] = False
-        return self.app(environ, start_response)
+        return self.next_app(environ, start_response)

Modified: Sandbox/shane/republish/zope/pipeline/apps/switch.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/switch.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/apps/switch.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -14,21 +14,36 @@
 
 
 from zope.interface import implements
+from zope.interface import providedBy
 from zope.publisher.interfaces import IWSGIApplication
 
-from zope.pipeline.entry import get_pipeline
+from zope.pipeline.entry import create_pipeline
+from zope.pipeline.interfaces import IPipelineParticipant
 
+
 class SwitchPipeline(object):
     """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.
     """
-    implements(IWSGIApplication)
+    implements(IWSGIApplication, IPipelineParticipant)
 
+    def __init__(self):
+        # _cache: {(interfaces provided by the request) -> pipeline}
+        self._cache = {}
+
+    def set_pipeline_params(self, name, pipeline_params):
+        self.pipeline_params = pipeline_params
+
     def __call__(self, environ, start_response):
         request = environ['zope.request']
-        app = get_pipeline(request=request)
-        return app(environ, start_response)
+        provided = tuple(providedBy(request))
+        pipeline = self._cache.get(provided)
+        if pipeline is None:
+            pipeline = create_pipeline(self.pipeline_params, provided)
+            self._cache[provided] = pipeline
+        return pipeline(environ, start_response)
 
     def __repr__(self):
         return '%s()' % self.__class__.__name__

Modified: Sandbox/shane/republish/zope/pipeline/apps/traversal.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/traversal.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/apps/traversal.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -12,7 +12,6 @@
 #
 ##############################################################################
 
-from zope.interface import adapts
 from zope.interface import implements
 from zope.publisher.interfaces import IWSGIApplication
 
@@ -23,15 +22,14 @@
     Requires 'zope.request' in the WSGI environment.
     """
     implements(IWSGIApplication)
-    adapts(IWSGIApplication)
 
-    def __init__(self, app):
-        self.app = app
+    def __init__(self, next_app):
+        self.next_app = next_app
 
     def __call__(self, environ, start_response):
         request = environ['zope.request']
         self.traverse(request)
-        return self.app(environ, start_response)
+        return self.next_app(environ, start_response)
 
     def traverse(self, request):
         traversal_stack = request.traversal_stack
@@ -63,6 +61,4 @@
 
 class HTTPTraverser(Traverser):
     implements(IWSGIApplication)
-    adapts(IWSGIApplication)
-    request_type = IHTTPRequest
 

Modified: Sandbox/shane/republish/zope/pipeline/apps/txnctl.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/txnctl.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/apps/txnctl.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -15,7 +15,6 @@
 
 import transaction
 from zope.location.interfaces import ILocationInfo
-from zope.interface import adapts
 from zope.interface import implements
 from zope.interface import providedBy
 from zope.publisher.interfaces import IRequest
@@ -27,15 +26,14 @@
     """WSGI middleware that begins and commits/aborts transactions.
     """
     implements(IWSGIApplication)
-    adapts(IWSGIApplication)
 
-    def __init__(self, app):
-        self.app = app
+    def __init__(self, next_app):
+        self.next_app = next_app
 
     def __call__(self, environ, start_response):
         transaction.begin()
         try:
-            res = self.app(environ, start_response)
+            res = self.next_app(environ, start_response)
         except:
             transaction.abort()
             raise
@@ -53,10 +51,9 @@
     Requires 'zope.request' in the environment.
     """
     implements(IWSGIApplication)
-    adapts(IWSGIApplication)
 
-    def __init__(self, app):
-        self.app = app
+    def __init__(self, next_app):
+        self.next_app = next_app
 
     def __call__(self, environ, start_response):
         res = self.app(environ, start_response)

Modified: Sandbox/shane/republish/zope/pipeline/configure.zcml
===================================================================
--- Sandbox/shane/republish/zope/pipeline/configure.zcml	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/configure.zcml	2009-02-14 08:50:36 UTC (rev 96519)
@@ -1,12 +1,12 @@
 <configure xmlns="http://namespaces.zope.org/zope"
            xmlns:wsgi="http://namespaces.zope.org/wsgi">
 
-<!-- a wsgi:pipeline directive specifies the names of
+<!-- A wsgi:pipeline directive specifies the names of
      applications to use in a pipeline.  Its implementation
      registers a bound PipelineApplicationList.adapt() method as an
      unnamed adapter from the request type to IPipelineApplicationList. -->
 
-<wsgi:pipeline for=".interfaces.IUndecidedRequest" names="
+<wsgi:pipeline for=".interfaces.INoRequest" names="
     virtual_host
     retry
     create_request
@@ -27,85 +27,80 @@
     call
     " />
 
-<!-- a wsgi:middleware directive registers an application for use
-     as middleware in a pipeline.  It is like an adapter directive,
-     except that the adapter is registered as requiring
-     two parameters, 'app' and 'request', while only the 'app' parameter is
-     provided to the middleware factory; the factory is wrapped
-     to make that happen.  If neither the directive nor the factory
+<!-- A wsgi:application directive registers an application for use
+     as an application in a pipeline.  If the application is the last
+     element of the pipeline, the factory will be passed no positional
+     parameters.  Otherwise, the application will be passed the next
+     application in the pipeline as the only positional parameter.
+
+     The implementation of this directive registers a new
+     WSGIApplicationFactory as an adapter from a marker request to
+     IWSGIApplicationFactory.  If neither the directive nor the factory
      says what kind of request is required, assume IRequest. -->
 
-<wsgi:middleware
-    factory=".apps.retry.Retry"
+<wsgi:application
     name="retry"
+    factory=".apps.retry.Retry"
     for=".interfaces.INoRequest" />
 
-<wsgi:middleware
-    factory=".apps.requestsetup.CreateRequest"
+<wsgi:application
     name="create_request"
+    factory=".apps.requestsetup.CreateRequest"
     for=".interfaces.INoRequest" />
 
-<!-- a wsgi:application directive registers an application for use
-     as the final application in a pipeline.  It is like an adapter
-     directive, except that the adapter is registered as requiring
-     one parameter, 'request', while no parameters are passed to the
-     application factory; the factory is wrapped
-     to make that happen.  If neither the directive nor the factory
-     says what kind of request is required, assume IRequest. -->
-
 <wsgi:application
-    factory=".apps.switch.SwitchPipeline"
     name="switch_pipeline"
+    factory=".apps.switch.SwitchPipeline"
     for=".interfaces.INoRequest" />
 
 
-<wsgi:middleware
-    factory="..."
-    name="log" />
+<wsgi:application
+    name="log"
+    factory="..." />
 
-<wsgi:middleware
-    factory=".apps.openroot.RootOpener"
-    name="open_root" />
+<wsgi:application
+    name="open_root"
+    factory=".apps.openroot.RootOpener" />
 
-<wsgi:middleware
-    factory=".apps.txnctl.TransactionController"
-    name="control_transaction" />
+<wsgi:application
+    name="control_transaction"
+    factory=".apps.txnctl.TransactionController" />
 
-<wsgi:middleware
-    factory=".apps.notify.EventNotifier"
-    name="event" />
+<wsgi:application
+    name="event"
+    factory=".apps.notify.EventNotifier" />
 
-<wsgi:middleware
-    factory="..."
-    name="handle_error" />
+<wsgi:application
+    name="handle_error"
+    factory="..." />
 
 <!-- no form processing for non-browser requests -->
-<wsgi:middleware
-    factory=".apps.passthrough"
+<wsgi:application
     name="process_form"
+    factory=".apps.passthrough"
     for="zope.publisher.interfaces.IRequest" />
 
 <!-- process forms for browser requests -->
-<wsgi:middleware
-    factory=".apps.requestsetup.ProcessForm"
+<wsgi:application
     name="process_form"
+    factory=".apps.requestsetup.ProcessForm"
     for="zope.publisher.interfaces.browser.IBrowserRequest" />
 
-<wsgi:middleware
-    factory=".apps.authenticate.Authenticator"
-    name="authenticate" />
+<wsgi:application
+    name="authenticate"
+    factory=".apps.authenticate.Authenticator" />
 
-<wsgi:middleware
-    factory=".apps.traversal.Traverser"
-    name="traverse" />
+<wsgi:application
+    name="traverse"
+    factory=".apps.traversal.Traverser" />
 
-<wsgi:middleware
-    factory=".apps.txnctl.TransactionAnnotator"
-    name="annotate_transaction" />
+<wsgi:application
+    name="annotate_transaction"
+    factory=".apps.txnctl.TransactionAnnotator" />
 
 <wsgi:application
-    factory=".apps.mapply.Caller"
-    name="call" />
+    name="call"
+    factory=".apps.mapply.Caller" />
 
 </configure>
 

Modified: Sandbox/shane/republish/zope/pipeline/entry.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/entry.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/entry.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -12,72 +12,42 @@
 #
 ##############################################################################
 """The main entry point for the zope.pipeline package.
-
-Use get_database_pipeline() or get_pipeline() to get a WSGI
-application built from a pipeline.
 """
 
-from zope.component import getMultiAdapter
 from zope.interface import directlyProvides
-from zope.interface import providedBy
+from zope.pipeline.interfaces import INoRequest
 from zope.pipeline.interfaces import IPipelineApplicationList
-from zope.pipeline.interfaces import IUndecidedRequest
-from zope.publisher import IWSGIApplication
-from zope.testing import cleanup
+from zope.pipeline.interfaces import IPipelineParticipant
+from zope.pipeline.interfaces import IWSGIApplicationFactory
 
-# _pipeline_cache: {(interfaces provided by the request) -> WSGI application}
-_pipeline_cache = {}
-cleanup.addCleanUp(_pipeline_cache.clear)
 
-def get_database_pipeline(database, global_environ=None):
-    """Get a pipeline that will connect to the given database.
+def create_pipeline(params, request_provides=None):
+    """Return a pipeline as a WSGI application.
 
-    The returned pipeline can be used for many requests, even
-    concurrently.
-    """
-    d = {}
-    if global_environ is not None:
-        d.update(global_environ)
-    d['zope.database'] = database
-    return get_pipeline(global_environ=global_environ)
+    The `params` contains a mapping of application name to
+    factory keyword parameter map.  An example `params` would be
+    ``{'open_root': {'database': zodb_db_object}}``.
 
-def get_pipeline(request=None, global_environ=None):
-    """Get a pipeline.
-
-    The returned pipeline can be used for many requests, even
-    concurrently.
+    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).
     """
-    if request is None:
-        provided = (IUndecidedRequest,)
-    else:
-        provided = tuple(providedBy(request))
-    pipeline = _pipeline_cache.get(provided)
-    if pipeline is None:
-        pipeline = make_pipeline(provided, global_environ)
-        _pipeline_cache[provided] = pipeline
-    return pipeline
+    if request_provides is None:
+        request_provides = (INoRequest,)
+    marker_request = MarkerRequest(request_provides)
+    app_list = IPipelineApplicationList(marker_request)
+    app = None
+    for name in reversed(app_list.names):
+        factory = IWSGIApplicationFactory(marker_request, name=name)
+        app = factory.create(name, params, app)
+        # If the app or some adapter needs to know the parameters
+        # for the whole pipeline, tell it.
+        participant = IPipelineParticipant(app, None)
+        if participant is not None:
+            participant.set_pipeline_params(app_name, params)
+    return app
 
-def make_pipeline(provided, global_environ=None):
-    marker_req = MarkerRequest(provided)
-    app_list = IPipelineApplicationList(marker_req)
-    names = list(app_list.names)  # make a copy
-    # The last name in the list is an application.
-    name = names.pop()
-    app = IWSGIApplication(marker_req, name=name)
-    while names:
-        # The rest of the names are middleware.
-        name = names.pop()
-        app = getMultiAdapter(
-            (app, marker_req), IWSGIApplication, name=name)
-    if global_environ:
-        # augment the WSGI environment with some data
-        def add_global_environ(environ, start_response):
-            environ.update(global_environ)
-            return inner_app(environ, start_response)
-        directlyProvides(add_global_environ, IWSGIApplication)
-        return add_global_environ
-    else:
-        return app
 
 class MarkerRequest(object):
     """A marker object that claims to provide a request type.

Modified: Sandbox/shane/republish/zope/pipeline/interfaces.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/interfaces.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/interfaces.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -19,8 +19,8 @@
 from zope.interface import Attribute
 from zope.interface import Interface
 
-class IUndecidedRequest(Interface):
-    """Indicates that no request has been created yet.
+class INoRequest(Interface):
+    """Indicates that no Zope request has been created yet.
 
     Use this interface to register WSGI applications in the
     pipeline that executes before the request creation step.
@@ -34,6 +34,32 @@
     """
     names = Attribute("Application names to use in a pipeline")
 
+class IWSGIApplicationFactory(Interface):
+    """Creates a WSGI application.
+
+    The two-step WSGI application creation process makes it
+    possible to pass parameters to the application constructor.
+    """
+    def create(app_name, pipeline_params, next_app=None):
+        """Create and return the application.
+
+        name is the name assigned to the application in this pipeline.
+
+        pipeline_params is a mapping of application name to parameter map.
+        In other words: {app_name: {param_name: param_value}}.
+
+        'next_app' is the next WSGI application in the pipeline.  It is
+        None when the application is the last in the pipeline.
+        """
+
+class IPipelineParticipant(Interface):
+    """Provides info for a WSGI app that needs to know about its pipeline.
+    """
+    def set_pipeline_params(app_name, pipeline_params):
+        """Tells the app about its name in the pipeline and all parameters.
+        """
+
+
 class IRequestFactoryRegistry(Interface):
     """A registry of request factories.
 

Modified: Sandbox/shane/republish/zope/pipeline/meta.zcml
===================================================================
--- Sandbox/shane/republish/zope/pipeline/meta.zcml	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/meta.zcml	2009-02-14 08:50:36 UTC (rev 96519)
@@ -16,13 +16,6 @@
 
 <meta:directive
     namespace="http://namespaces.zope.org/wsgi"
-    name="middleware"
-    schema=".zcml.IMiddlewareDirective"
-    handler=".zcml.middleware"
-    />
-
-<meta:directive
-    namespace="http://namespaces.zope.org/wsgi"
     name="request-factory"
     schema=".zcml.IRequestFactoryDirective"
     handler=".zcml.request_factory"

Modified: Sandbox/shane/republish/zope/pipeline/zcml.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/zcml.py	2009-02-14 07:14:39 UTC (rev 96518)
+++ Sandbox/shane/republish/zope/pipeline/zcml.py	2009-02-14 08:50:36 UTC (rev 96519)
@@ -31,6 +31,7 @@
 from zope.schema import TextLine
 
 from zope.pipeline.interfaces import IPipelineApplicationList
+from zope.pipeline.interfaces import IWSGIApplicationFactory
 from zope.pipeline.apps.requestsetup import factoryRegistry
 
 
@@ -40,98 +41,92 @@
     for_ = Tokens(
         title=u'Request types',
         description=u'The request types that should use this pipeline',
-        value_type=GlobalObject(),
-        required=True)
+        value_type=GlobalObject())
 
     names = Tokens(
         title=u'Application names',
-        description=u'The list of WSGI application names to use.  '
-            'The last name in the list declares a simple application; '
-            'the rest declare a middleware application.',
-        required=True)
+        description=(
+            u'The list of WSGI application names to use. '
+            u'The last name in the list declares a simple application; '
+            u'the rest declare a middleware application.'))
 
+class PipelineApplicationList(object):
+    implements(IPipelineApplicationList)
 
-class IApplicationDirective(Interface):
-    """Declare a simple WSGI application for use at the end of a pipeline"""
+    def __init__(self, names):
+        self.names = names
 
-    factory = GlobalObject(
-        title=u"Application factory",
-        description=(u"A factory that creates the WSGI application.  "
-                     u"The factory will be called with no parameters."),
-        required=True,
-        )
+    def adapt(self, marker_request):
+        """Called by adapter lookup"""
+        return self
 
+def pipeline(_context, for_, names):
+    """Register a pipeline application list"""
+    obj = PipelineApplicationList(names)
+    adapter(_context, factory=obj.adapt,
+        provides=[IPipelineApplicationList], for_=for_)
+
+
+
+class IApplicationDirective(Interface):
+    """Declare a WSGI application."""
+
     name = TextLine(
         title=u"Name",
-        description=u"The name of the application",
-        required=True,
-        )
+        description=u"The name of the application")
 
+    factory = GlobalObject(
+        title=u"Application factory",
+        description=(
+            u"A factory that creates the WSGI application. "
+            u"If the application is used in the pipeline as "
+            u"middleware, the factory will be passed a single "
+            u"positional parameter containing the next "
+            u"application in the pipeline.  Other parameters "
+            u"specified for the pipeline may also be passed as "
+            u"keyword parameters."))
+
     for_ = Tokens(
         title=u'Request types',
         description=u'The request types that should use this application',
         value_type=GlobalObject(),
         required=False)
 
+class WSGIApplicationFactory(object):
+    implements(IWSGIApplicationFactory)
 
-class IMiddlewareDirective(IApplicationDirective):
-    """Declare a middleware WSGI application for use in a pipeline"""
-    factory = GlobalObject(
-        title=u"Application factory",
-        description=(u"A factory that creates the WSGI application.  "
-                     u"The factory will be called with one parameter: "
-                     u"the next application in the pipeline."),
-        required=True,
-        )
+    def __init__(self, app_factory):
+        self.app_factory = app_factory
 
-
-class PipelineApplicationList(object):
-    implements(IPipelineApplicationList)
-
-    def __init__(self, names):
-        self.names = names
-
-    def adapt(self, request):
+    def adapt(self, marker_request):
         """Called by adapter lookup"""
         return self
 
+    def create(self, app_name, pipeline_params, next_app=None):
+        kw = pipeline_params.get(app_name)
+        if kw is None:
+            kw = {}
+        if next_app is not None:
+            # middleware
+            app = self.app_factory(next_app, **kw)
+        else:
+            # final app
+            app = self.app_factory(**kw)
+        return app
 
-def pipeline(_context, for_, names):
-    """Register a pipeline application list"""
-    obj = PipelineApplicationList(names)
-    adapter(_context, factory=obj.adapt,
-        provides=[IPipelineApplicationList], for_=for_)
-
 def application(_context, factory, name, for_=()):
-    """Register a simple WSGI app for use at the end of pipeline"""
+    """Register a WSGI application"""
     if not for_:
-        for_ = [getattr(factory, 'request_type', IRequest)]
-
-    def app_factory(marker_request):
-        res = factory()
-        if not IWSGIApplication.providedBy(res):
-            alsoProvides(res, IWSGIApplication)
-        return res
-
-    adapter(_context, factory=[app_factory], provides=[IWSGIApplication],
+        for_ = [IRequest]
+    factory_factory = WSGIApplicationFactory(factory)
+    adapter(_context,
+        factory=[factory_factory.adapt],
+        provides=[IWSGIApplicationFactory],
         for_=for_, name=name)
 
-def middleware(_context, factory, name, for_=()):
-    """Register a middleware WSGI app for use in a pipeline"""
-    if not for_:
-        for_ = [getattr(factory, 'request_type', IRequest)]
-    for_ = [IWSGIApplication] + list(for_)
 
-    def app_factory(app, marker_request):
-        res = factory(app)
-        if not IWSGIApplication.providedBy(res):
-            alsoProvides(res, IWSGIApplication)
-        return res
 
-    adapter(_context, factory=[app_factory], provides=[IWSGIApplication],
-        for_=for_, name=name)
 
-
 class IRequestFactoryDirective(Interface):
     """Link information from a request to a request factory"""
 



More information about the Checkins mailing list