[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