[Checkins] SVN: Sandbox/shane/republish/zope/pipeline/ checkpoint

Shane Hathaway shane at hathawaymix.org
Thu Feb 12 03:29:03 EST 2009


Log message for revision 96461:
  checkpoint
  

Changed:
  U   Sandbox/shane/republish/zope/pipeline/apps/__init__.py
  D   Sandbox/shane/republish/zope/pipeline/apps/passthrough.py
  D   Sandbox/shane/republish/zope/pipeline/apps/requestfactory.py
  A   Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py
  U   Sandbox/shane/republish/zope/pipeline/configure.zcml
  A   Sandbox/shane/republish/zope/pipeline/entry.py
  U   Sandbox/shane/republish/zope/pipeline/meta.zcml
  U   Sandbox/shane/republish/zope/pipeline/zcml.py

-=-
Modified: Sandbox/shane/republish/zope/pipeline/apps/__init__.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/__init__.py	2009-02-12 07:29:55 UTC (rev 96460)
+++ Sandbox/shane/republish/zope/pipeline/apps/__init__.py	2009-02-12 08:29:02 UTC (rev 96461)
@@ -1 +1,23 @@
-"""Various WSGI applications and middleware used in a standard Zope pipeline"""
+##############################################################################
+#
+# 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 applications and middleware used in a standard Zope pipeline"""
+
+def passthrough(app):
+    """A passthrough application.
+
+    Use this to skip a pipeline step.  Register this function
+    as the middleware factory for a step you don't want to use.
+    The step will not exist at all in the resulting pipeline.
+    """
+    return app

Deleted: Sandbox/shane/republish/zope/pipeline/apps/passthrough.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/passthrough.py	2009-02-12 07:29:55 UTC (rev 96460)
+++ Sandbox/shane/republish/zope/pipeline/apps/passthrough.py	2009-02-12 08:29:02 UTC (rev 96461)
@@ -1,23 +0,0 @@
-##############################################################################
-#
-# 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.
-#
-##############################################################################
-
-
-def passthrough(app):
-    """A passthrough application.
-
-    Use this to skip a pipeline step.  Register this function
-    as the middleware factory for a step you don't want to use.
-    The step will be eliminated even from application stack traces.
-    """
-    return app

Copied: Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py (from rev 96459, Sandbox/shane/republish/zope/pipeline/apps/requestfactory.py)
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py	2009-02-12 08:29:02 UTC (rev 96461)
@@ -0,0 +1,181 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""The request setup WSGI app and the registry for IRequest factories.
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.configuration.exceptions import ConfigurationError
+from zope.interface import adapts
+from zope.interface import implements
+from zope.publisher.interfaces import IWSGIApplication
+from zope.testing import cleanup
+
+from zope.pipeline.interfaces import IRequestFactoryRegistry
+
+
+class CreateRequest(object):
+    """WSGI middleware that creates a request and puts it in the environment.
+
+    Chooses the type of request based on the content of the WSGI
+    environment.
+    """
+    implements(IWSGIApplication)
+    adapts(IWSGIApplication)
+
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        scheme = environ.get('wsgi.url_scheme', 'http').lower()
+        method = environ.get('REQUEST_METHOD', 'GET').upper()
+        mimetype = environ.get('CONTENT_TYPE', '')
+        request = factoryRegistry.make_request(
+            scheme, method, mimetype, environ)
+        environ['zope.request'] = request
+
+        if IBrowserRequest.providedBy(request):
+            # only browser requests have skins
+            setDefaultSkin(request)
+
+        return self.app(environ, start_response)
+
+
+class ProcessForm(object):
+    """WSGI middleware that processes HTML form data.
+
+    Requires the environment to contain a 'zope.request' that
+    is an IHTTPRequest, not just an IRequest.
+    """
+    implements(IWSGIApplication)
+    adapts(IWSGIApplication)
+    request_type = IHTTPRequest
+
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        request = environ['zope.request']
+
+        def to_unicode(text):
+            charsets = request.charsets
+            if not charsets:
+                envadapter = IUserPreferredCharsets(request)
+                charsets = envadapter.getPreferredCharsets() or ['utf-8']
+                request.charsets = charsets
+
+            for charset in charsets:
+                try:
+                    return unicode(text, charset)
+                except UnicodeError:
+                    pass
+            raise UnicodeError(
+                "Unable to decode %s using any available character set"
+                % repr(text))
+
+        parser = FormParser(environ, to_unicode=to_unicode)
+        request.form = parser.parse()
+
+        if parser.action:
+            request.traversal_stack.insert(0, parser.action)
+
+        return self.app(environ, start_response)
+
+
+class RequestFactoryRegistry(object):
+    """The registry implements a four stage lookup for registered factories
+    that have to deal with requests::
+
+        {scheme -> {method > { mimetype ->
+            [
+                {'priority' : some_int,
+                 'factory' :  factory,
+                 'name' : some_name }, ...
+            ]
+        }}}
+
+    The `priority` is used to define a lookup-order when multiple factories
+    are registered for the same scheme, method, and mime-type.
+    """
+    implements(IRequestFactoryRegistry)
+
+    def __init__(self):
+        self._d = {}   # {scheme->{method->{mimetype->{factories_data}}}}
+
+    def register(self, scheme, method, mimetype, name, priority, factory):
+        """Register a factory for scheme + method + mimetype """
+
+        # initialize the three-level deep nested datastructure if necessary
+        m = self._d.setdefault(scheme, {})
+        m = m.setdefault(method, {})
+        l = m.setdefault(mimetype, [])
+
+        # Check if there is already a registered request factory (check by
+        # name).  If yes then it will be removed and replaced by a new
+        # factory.
+        for pos, d in enumerate(l):
+            if d['name'] == name:
+                del l[pos]
+                break
+        # add the publisher factory + additional informations
+        l.append({'name' : name, 'factory' : factory, 'priority' : priority})
+
+        # order by descending priority
+        l.sort(lambda x,y: -cmp(x['priority'], y['priority']))
+
+        # check if the priorities are unique
+        priorities = [item['priority'] for item in l]
+        if len(set(priorities)) != len(l):
+            raise ConfigurationError('All registered publishers for a given '
+                                     'method+mimetype must have distinct '
+                                     'priorities. Please check your ZCML '
+                                     'configuration')
+
+    def getFactoriesFor(self, scheme, method, mimetype):
+        if ';' in mimetype:
+            # `mimetype` might be something like 'text/xml; charset=utf8'. In
+            # this case we are only interested in the first part.
+            mimetype = mimetype.split(';', 1)[0]
+
+        try:
+            return self._d[scheme][method][mimetype.strip()]
+        except KeyError:
+            return None
+
+
+    def make_request(self, scheme, method, mimetype, environment):
+        """Get a factory given scheme+method+mimetype and an environment."""
+
+        variations = [
+            (scheme, method, mimetype),
+            (scheme, method, '*'),
+            (scheme, '*', '*')
+            ]
+        for s, m, mt in variations:
+            factory_lst = self.getFactoriesFor(s, m, mt)
+            if factory_lst:
+                # Try to call each factory.  If the factory can't or
+                # doesn't want to handle the given environment, it should
+                # return None.
+                for d in factory_lst:
+                    factory = d['factory']
+                    request = factory(environment)
+                    if request is not None:
+                        return request
+        raise ConfigurationError('No registered request factory found '
+            'for (%s/%s/%s)' % (scheme, method, mimetype))
+
+factoryRegistry = RequestFactoryRegistry()
+
+cleanup.addCleanUp(lambda : factoryRegistry.__init__())
+

Modified: Sandbox/shane/republish/zope/pipeline/configure.zcml
===================================================================
--- Sandbox/shane/republish/zope/pipeline/configure.zcml	2009-02-12 07:29:55 UTC (rev 96460)
+++ Sandbox/shane/republish/zope/pipeline/configure.zcml	2009-02-12 08:29:02 UTC (rev 96461)
@@ -1,39 +1,30 @@
 <configure xmlns="http://namespaces.zope.org/zope"
            xmlns:wsgi="http://namespaces.zope.org/wsgi">
 
-<!-- a wsgi:application-list directive specifies the names of
+<!-- a wsgi:pipeline directive specifies the names of
      applications to use in a pipeline.  Its implementation
-     registers a bound ApplicationList.adapt() method as an
-     unnamed adapter from the request type to IApplicationList. -->
+     registers a bound PipelineApplicationList.adapt() method as an
+     unnamed adapter from the request type to IPipelineApplicationList. -->
 
-<wsgi:application-list for=".interfaces.IUndecidedRequest" names="
+<wsgi:pipeline for=".interfaces.IUndecidedRequest" names="
     retry
-    request_factory
+    create_request
     switch_pipeline
     " />
 
-<wsgi:application-list for="zope.publisher.interfaces.IRequest" names="
-    logger
-    root_opener
-    transaction_controller
-    notifier
-    error_handler
-    authenticator
-    traverser
-    transaction_annotator
-    caller
+<wsgi:pipeline for="zope.publisher.interfaces.IRequest" names="
+    log
+    open_root
+    control_transaction
+    event
+    handle_error
+    process_form
+    authenticate
+    traverse
+    annotate_transaction
+    call
     " />
 
-<!-- a wsgi:pipeline directive creates a pipeline for a particular
-     request type.  Its implementation registers a bound
-     Pipeline.adapt() method as an adapter from the request type
-     to IWSGIApplication with name "pipeline". -->
-
-<wsgi:pipeline for=".interfaces.IUndecidedRequest" />
-<wsgi:pipeline for="zope.publisher.interfaces.IRequest" />
-<wsgi:pipeline for="zope.publisher.interfaces.http.IHTTPRequest" />
-<wsgi:pipeline for="zope.publisher.interfaces.browser.IBrowserRequest" />
-
 <!-- 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
@@ -49,7 +40,7 @@
 
 <wsgi:middleware
     factory="..."
-    name="request_factory"
+    name="request_setup"
     for=".interfaces.INoRequest" />
 
 <!-- a wsgi:application directive registers an application for use

Added: Sandbox/shane/republish/zope/pipeline/entry.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/entry.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/entry.py	2009-02-12 08:29:02 UTC (rev 96461)
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""The main entry point for the zope.pipeline package.
+
+Use get_pipeline() to get a WSGI application built from a Zope pipeline.
+"""
+
+# _pipeline_cache: {(interfaces provided by the request) -> WSGI application}
+_pipeline_cache = {}
+
+def get_pipeline(request=None):
+    if request is None:
+        provided = (IUndecidedRequest,)
+    else:
+        provided = tuple(providedBy(request))
+    pipeline = _pipeline_cache.get(provided)
+    if pipeline is None:
+        pipeline = make_pipeline(provided)
+        _pipeline_cache[provided] = pipeline
+    return pipeline
+
+def make_pipeline(provided):
+    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)
+    return app
+
+class MarkerRequest(object):
+    """A marker object that claims to provide a request type.
+
+    This is used for adapter lookup.
+    """
+    __slots__ = ('__provides__',)
+
+    def __init__(self, request_type):
+        directlyProvides(self, request_type)

Modified: Sandbox/shane/republish/zope/pipeline/meta.zcml
===================================================================
--- Sandbox/shane/republish/zope/pipeline/meta.zcml	2009-02-12 07:29:55 UTC (rev 96460)
+++ Sandbox/shane/republish/zope/pipeline/meta.zcml	2009-02-12 08:29:02 UTC (rev 96461)
@@ -2,13 +2,6 @@
 
 <meta:directive
     namespace="http://namespaces.zope.org/wsgi"
-    name="application-list"
-    schema=".zcml.IApplicationListDirective"
-    handler=".zcml.application_list"
-    />
-
-<meta:directive
-    namespace="http://namespaces.zope.org/wsgi"
     name="pipeline"
     schema=".zcml.IPipelineDirective"
     handler=".zcml.pipeline"
@@ -29,7 +22,7 @@
     />
 
 <meta:directive
-    namespace="http://namespaces.zope.org/zope"
+    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-12 07:29:55 UTC (rev 96460)
+++ Sandbox/shane/republish/zope/pipeline/zcml.py	2009-02-12 08:29:02 UTC (rev 96461)
@@ -25,55 +25,48 @@
 from zope.interface import Attribute
 from zope.interface import directlyProvides
 from zope.interface import Interface
-from zope.schema import TextLine
-
 from zope.publisher.interfaces import IWSGIApplication
 from zope.publisher.interfaces import IRequest
+from zope.schema import Int
+from zope.schema import TextLine
 
+from zope.pipeline.apps.requestsetup import factoryRegistry
 
-class IApplicationListDirective(Interface):
+
+class IPipelineDirective(Interface):
     """Declare a list of application names in a WSGI pipeline"""
 
     for_ = GlobalObject(
-        title=_('Request type'),
-        description=_('The type of request that should use this app list'),
+        title=u'Request type',
+        description=u'The type of request that should use this app list',
         required=True)
 
     names = Tokens(
-        title=_('Application names'),
-        description=_('The list of WSGI application names to use.  '
+        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.'),
+            'the rest declare a middleware application.',
         required=True)
 
 
-class IPipelineDirective(Interface):
-    """Declare a WSGI pipeline"""
-
-    for_ = GlobalObject(
-        title=_('Request type'),
-        description=_('The type of request that should use this pipeline'),
-        required=True)
-
-
 class IApplicationDirective(Interface):
     """Declare a simple WSGI application for use at the end of a pipeline"""
 
     factory = GlobalObject(
-        title=_("Application factory"),
-        description=_("A factory that creates the WSGI application."),
+        title=u"Application factory",
+        description=u"A factory that creates the WSGI application.",
         required=True,
         )
 
     name = TextLine(
-        title=_("Name"),
-        description=_("The name of the application"),
+        title=u"Name",
+        description=u"The name of the application",
         required=True,
         )
 
     for_ = GlobalObject(
-        title=_("Request type"),
-        description=_("The type of request this application expects"),
+        title=u"Request type",
+        description=u"The request type that triggers use of this application",
         required=False,
         )
 
@@ -84,12 +77,12 @@
     pass
 
 
-class IApplicationList(Interface):
+class IPipelineApplicationList(Interface):
     names = Attribute("Application names to use in a pipeline")
 
 
-class ApplicationList(object):
-    implements(IApplicationList)
+class PipelineApplicationList(object):
+    implements(IPipelineApplicationList)
 
     def __init__(self, names):
         self.names = names
@@ -99,64 +92,12 @@
         return self
 
 
-class MarkerRequest(object):
-    """A marker object that claims to provide a request type.
+def pipeline(_context, for_, names):
+    """Register a pipeline application list"""
+    obj = PipelineApplicationList(names)
+    adapter(_context, factory=obj.adapt,
+        provides=[IPipelineApplicationList], for_=[for_])
 
-    This is used for adapter lookup.
-    """
-    __slots__ = ('__provides__',)
-
-    def __init__(self, request_type):
-        directlyProvides(self, request_type)
-
-
-class Pipeline(object):
-    implements(IWSGIApplication)
-
-    def __init__(self, request_type):
-        self.request_type = request_type
-        self.app = None
-
-    def adapt(self, request):
-        """Called by adapter lookup"""
-        app = self.app
-        if app is None:
-            # cache the pipeline for future use
-            self.app = app = self.make_app()
-        return app
-
-    def make_app(self):
-        marker_req = MarkerRequest(self.request_type)
-        app_list = IApplicationList(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)
-        return app
-
-    def __repr__(self):
-        if self.app is None:
-            self.app = self.make_app()
-        return '%s(%s, app=%s)' % (
-            self.__class__.__name__, repr(self.request_type), repr(self.app))
-
-
-def application_list(_context, for_, names):
-    """Register an application list"""
-    obj = ApplicationList(names)
-    adapter(_context, factory=obj.adapt, provides=[IAppList], for_=[for_])
-
-def pipeline(_context, for_):
-    """Register a pipeline"""
-    obj = Pipeline(for_)
-    adapter(_context, factory=obj.adapt, provides=[IWSGIApplication],
-        for_=[for_], name='pipeline')
-
 def application(_context, factory, name, for_=None):
     """Register a simple WSGI app for use at the end of pipeline"""
     if for_ is None:
@@ -190,23 +131,29 @@
     """Link information from a request to a request factory"""
 
     name = TextLine(
-        title=_('Name'),
-        description=_('The name of the request factory.'))
+        title=u'Name',
+        description=u'The name of the request factory.')
 
     factory = GlobalObject(
-        title=_('Factory'),
-        description=_('The request factory'))
+        title=u'Factory',
+        description=(u'The request factory, which is a callable '
+                     u'that accepts one parameter, the WSGI environment, '
+                     u'and returns a request object. '
+                     u'The factory can return None to defer to the '
+                     u'next registered factory.'),
+        required=True)
 
-    protocols = Tokens(
-        title=_('Protocols'),
-        description=_('A list of protocols to support.  Defaults to "HTTP".'),
+    schemes = Tokens(
+        title=u'URL Schemes',
+        description=(u'A list of URL schemes to support. This matches the '
+                     u'wsgi.url_scheme parameter. Defaults to "http https".'),
         value_type=TextLine(),
         required=False)
 
     methods = Tokens(
         title=u'Methods',
         description=(u'A list of HTTP method names. If the method is a "*", '
-                     u'then all methods will match. Example: "GET POST"'),
+                     u'then all methods will match. Example: "GET POST"',
         value_type=TextLine(),
         required=False)
 
@@ -225,15 +172,15 @@
         required=False)
 
 def request_factory(_context, name, factory,
-    protocols=['HTTP'], methods=['*'], mimetypes=['*'], priority=0):
+    schemes=['http', 'https'], methods=['*'], mimetypes=['*'], priority=0):
 
     factory = factory()
 
-    for protocol in protocols:
+    for scheme in schemes:
         for method in methods:
             for mimetype in mimetypes:
                 _context.action(
-                    discriminator = (method, mimetype, priority),
+                    discriminator = (scheme, method, mimetype, priority),
                     callable = factoryRegistry.register,
-                    args = (protocol, method, mimetype, name, priority, factory)
+                    args = (scheme, method, mimetype, name, priority, factory)
                     )



More information about the Checkins mailing list