[Checkins] SVN: Sandbox/shane/republish/zope/p Configure the pipeline using zope.component :-)

Shane Hathaway shane at hathawaymix.org
Tue Feb 10 21:02:49 EST 2009


Log message for revision 96434:
  Configure the pipeline using zope.component :-)
  

Changed:
  A   Sandbox/shane/republish/zope/pipeline/autotemp.py
  A   Sandbox/shane/republish/zope/pipeline/build.py
  U   Sandbox/shane/republish/zope/pipeline/caller.py
  A   Sandbox/shane/republish/zope/pipeline/configure.zcml
  D   Sandbox/shane/republish/zope/pipeline/dbopener.py
  A   Sandbox/shane/republish/zope/pipeline/retry.py
  D   Sandbox/shane/republish/zope/pipeline/standard.py
  A   Sandbox/shane/republish/zope/pipeline/traversalroot.py
  D   Sandbox/shane/republish/zope/pipeline/txncontroller.py
  A   Sandbox/shane/republish/zope/pipeline/txnmiddle.py
  U   Sandbox/shane/republish/zope/publisher/interfaces/base.py
  U   Sandbox/shane/republish/zope/publisher/interfaces/http.py

-=-
Added: Sandbox/shane/republish/zope/pipeline/autotemp.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/autotemp.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/autotemp.py	2009-02-11 02:02:48 UTC (rev 96434)
@@ -0,0 +1,39 @@
+
+from cStringIO import StringIO
+
+bufsize = 8192
+
+class AutoTemporaryFile(object):
+    """Initially a StringIO, but becomes a TemporaryFile if it grows big"""
+    def __init__(self, threshold=bufsize):
+        self._threshold = threshold
+        self._f = f = StringIO()
+        # delegate most methods
+        for m in ('read', 'seek', 'tell', 'close'):
+            setattr(self, m, getattr(f, m))
+
+    def write(self, data):
+        if self.tell() + len(data) >= self._threshold:
+            # convert to TemporaryFile
+            f = tempfile.TemporaryFile()
+            f.write(self._f.getvalue())
+            f.seek(self.tell())
+            self._f = f
+            # delegate all important methods
+            for m in ('write', 'read', 'seek', 'tell', 'close'):
+                setattr(self, m, getattr(f, m))
+        self._f.write(data)
+
+    def copyfrom(self, src):
+        while True:
+            data = src.read(bufsize)
+            if not data:
+                break
+            self.write(data)
+
+    def copyto(self, dest):
+        while True:
+            data = self.read(bufsize)
+            if not data:
+                break
+            dest.write(data)

Copied: Sandbox/shane/republish/zope/pipeline/build.py (from rev 96373, Sandbox/shane/republish/zope/pipeline/standard.py)
===================================================================
--- Sandbox/shane/republish/zope/pipeline/build.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/build.py	2009-02-11 02:02:48 UTC (rev 96434)
@@ -0,0 +1,34 @@
+
+"""Creates the WSGI pipeline configured by ZCML and the component architecture.
+
+
+"""
+
+from zope.publisher.interfaces import IWSGIApplication
+
+standard_stages = []  # set by pipeline:stages directive
+
+def make_app(configuration, stages=standard_stages):
+    stages = list(stages)  # make a copy
+    name = stages.pop()
+    app = build_stage(None, name, configuration)
+    while stages:
+        name = stages.pop()
+        app = build_stage(app, name, configuration)
+    return app
+
+def build_stage(app, name, configuration):
+    res = None
+    # look for an app that requires the configuration
+    if app is None:
+        res = IWSGIApplication(configuration, name=name, default=None)
+    else:
+        res = IWSGIApplication(app, configuration, name=name, default=None)
+    if res is not None:
+        return res
+    # look for an app that requires no configuration
+    if app is None:
+        res = IWSGIApplication(name=name)
+    else:
+        res = IWSGIApplication(app, name=name)
+    return res


Property changes on: Sandbox/shane/republish/zope/pipeline/build.py
___________________________________________________________________
Added: svn:mergeinfo
   + 

Modified: Sandbox/shane/republish/zope/pipeline/caller.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/caller.py	2009-02-10 23:52:56 UTC (rev 96433)
+++ Sandbox/shane/republish/zope/pipeline/caller.py	2009-02-11 02:02:48 UTC (rev 96434)
@@ -5,6 +5,7 @@
     """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.
     """
     def __call__(self, environ, start_response):
         request = environ['zope.request']

Added: Sandbox/shane/republish/zope/pipeline/configure.zcml
===================================================================
--- Sandbox/shane/republish/zope/pipeline/configure.zcml	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/configure.zcml	2009-02-11 02:02:48 UTC (rev 96434)
@@ -0,0 +1,44 @@
+
+
+<!--
+    # other possible stages:
+    # RequestProfiler
+    # CodeFreshnessChecker
+    # Multiprocessor
+    # ComponentConfigurator
+    # DetailedTracebackGenerator
+
+    # stages we're using:
+    'request_logger',
+    'retry',
+    'request_creator',        # also sets locale (?)
+    'traversal_root_opener',
+    'transaction_controller',
+    'app_error_handler',
+    'global_authenticator',
+    # Note: the traverser also has to invoke authentication at each step.
+    'traverser',
+    'transaction_annotator',
+    'caller',
+-->
+
+<pipeline:stages names="
+    request_logger
+    retry
+    request_creator
+    traversal_root_opener
+    transaction_controller
+    app_error_handler
+    global_authenticator
+    traverser
+    transaction_annotator
+    caller
+    " />
+
+<zope:adapter
+    factory=".txnmiddle.TransactionController"
+    name="transaction_controller" />
+
+<zope:adapter
+    factory=".txnmiddle.TransactionAnnotator"
+    name="transaction_annotator" />

Added: Sandbox/shane/republish/zope/pipeline/retry.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/retry.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/retry.py	2009-02-11 02:02:48 UTC (rev 96434)
@@ -0,0 +1,62 @@
+
+from zope.publisher.interfaces import IWSGIApplication
+from zope.publisher.interfaces.exceptions import Retry
+from ZODB.POSException import ConflictError
+
+from zope.pipeline.autotemp import AutoTemporaryFile
+
+
+class Retry(object):
+    """Retries requests when a Retry or ConflictError propagates.
+
+    This middleware 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 has
+    a true value.
+    """
+    implements(IWSGIApplication)
+    adapts(IWSGIApplication)
+
+    def __init__(self, app, max_attempts=3):
+        self.app = app
+        self.max_attempts = max_attempts
+
+    def __call__(self, environ, start_response):
+        wsgi_input = environ.get('wsgi.input')
+        if wsgi_input is not None:
+            if not hasattr(wsgi_input, 'seek'):
+                # make the input stream rewindable
+                f = AutoTemporaryFile()
+                f.copyfrom(wsgi_input)
+                environ['wsgi.input'] = wsgi_input = f
+
+        def retryable_start_response(status, response_headers, exc_info=None):
+            start_response_params[:] = [status, response_headers, exc_info]
+            tmp = AutoTemporaryFile()
+            output_file[:] = [tmp]
+            return tmp
+
+        attempt = 1
+        while attempt < self.max_attempts:
+            start_response_params = []
+            output_file = []
+            environ['zope.can_retry'] = True
+            try:
+                res = self.app(environ, retryable_start_response)
+                if start_response_params:
+                    dest = start_response(*tuple(start_response_params))
+                    src = output_file[0]
+                    src.seek(0)
+                    src.copyto(dest)
+                    src.close()
+                return res
+            except (Retry, ConflictError):
+                if 'zope.request' in environ:
+                    del environ['zope.request']
+                if wsgi_input is not None:
+                    wsgi_input.seek(0)
+                attempt += 1
+
+        # try once more, this time without retry support
+        environ['zope.can_retry'] = False
+        return self.app(environ, start_response)

Deleted: Sandbox/shane/republish/zope/pipeline/standard.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/standard.py	2009-02-10 23:52:56 UTC (rev 96433)
+++ Sandbox/shane/republish/zope/pipeline/standard.py	2009-02-11 02:02:48 UTC (rev 96434)
@@ -1,28 +0,0 @@
-
-"""Creates a standard Zope WSGI publishing pipeline."""
-
-from zope.pipeline.call import Caller
-
-standard_pipeline = (
-    # RequestProfiler,
-    # CodeFreshnessChecker,
-    # Multiprocessor,
-    # ComponentConfigurator,
-    # DetailedTracebackGenerator,
-    RequestLogger,
-    RequestCreator,         # also sets locale (?)
-    DatabaseOpener,
-    TransactionController,  # includes retry logic and annotation
-    choose_traverser,       # complex or simple traversal based on zope.conf
-    AppErrorHandler,
-    Caller
-)
-
-def make_app(zope_conf, pipeline=standard_pipeline):
-    p = list(pipeline)
-    factory = p.pop()
-    app = factory(zope_conf)
-    while p:
-        factory, kw = p.pop()
-        app = factory(app, zope_conf)
-    return app

Copied: Sandbox/shane/republish/zope/pipeline/traversalroot.py (from rev 96372, Sandbox/shane/republish/zope/pipeline/dbopener.py)
===================================================================
--- Sandbox/shane/republish/zope/pipeline/traversalroot.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/traversalroot.py	2009-02-11 02:02:48 UTC (rev 96434)
@@ -0,0 +1,7 @@
+
+
+class TraversalRootOpener(object):
+
+    def __init__(self, app, zope_conf):
+        self.app = app
+        
\ No newline at end of file

Copied: Sandbox/shane/republish/zope/pipeline/txnmiddle.py (from rev 96372, Sandbox/shane/republish/zope/pipeline/txncontroller.py)
===================================================================
--- Sandbox/shane/republish/zope/pipeline/txnmiddle.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/txnmiddle.py	2009-02-11 02:02:48 UTC (rev 96434)
@@ -0,0 +1,92 @@
+
+
+import transaction
+from zope.location.interfaces import ILocationInfo
+from zope.interface import providedBy
+from zope.interface import adapts
+from zope.interface import implements
+from zope.publisher.interfaces import IRequest
+from zope.publisher.interfaces import IWSGIApplication
+from zope.security.proxy import removeSecurityProxy
+
+
+class TransactionController(object):
+    """WSGI middleware that manages transactions.
+    """
+    implements(IWSGIApplication)
+    adapts(IWSGIApplication)
+
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        transaction.begin()
+        try:
+            res = self.app(environ, start_response)
+        except:
+            transaction.abort()
+            raise
+        txn = transaction.get()
+        if txn.isDoomed():
+            txn.abort()
+        else:
+            txn.commit()
+        return res
+
+
+class TransactionAnnotator(object):
+    """WSGI middleware that annotates transactions.
+
+    Requires 'zope.request' in the environment.
+    """
+    implements(IWSGIApplication)
+    adapts(IWSGIApplication)
+
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        res = self.app(environ, start_response)
+        txn = transaction.get()
+        if not txn.isDoomed():
+            request = environ['zope.request']
+            name, ob = request.traversed[-1]
+            self.annotate(txn, request, ob)
+        return res
+
+    def annotate(self, txn, request, ob):
+        """Set some useful meta-information on the transaction.
+
+        This information is used by the undo framework, for example.
+        """
+        if request.principal is not None:
+            txn.setUser(request.principal.id)
+
+        # Work around methods that are usually used for views
+        bare = removeSecurityProxy(ob)
+        if isinstance(bare, instancemethod):
+            ob = bare.im_self
+
+        # set the location path
+        path = None
+        location = ILocationInfo(ob, None)
+        if location is not None:
+            # Views are made children of their contexts, but that
+            # doesn't necessarily mean that we can fully resolve the
+            # path. E.g. the family tree of a resource cannot be
+            # resolved completely, as the site manager is a dead end.
+            try:
+                path = location.getPath()
+            except (AttributeError, TypeError):
+                pass
+        if path is not None:
+            txn.setExtendedInfo('location', path)
+
+        # set the request type
+        iface = IRequest
+        for iface in providedBy(request):
+            if iface.extends(IRequest):
+                break
+        iface_dotted = iface.__module__ + '.' + iface.getName()
+        txn.setExtendedInfo('request_type', iface_dotted)
+        return txn

Modified: Sandbox/shane/republish/zope/publisher/interfaces/base.py
===================================================================
--- Sandbox/shane/republish/zope/publisher/interfaces/base.py	2009-02-10 23:52:56 UTC (rev 96433)
+++ Sandbox/shane/republish/zope/publisher/interfaces/base.py	2009-02-11 02:02:48 UTC (rev 96434)
@@ -5,7 +5,8 @@
 from zope.interface import Interface
 from zope.interface.common.mapping import IExtendedReadMapping
 
-__all__ = ('IRequest', 'IResponse', 'IResult', 'IHeld', 'IDebugFlags')
+__all__ = ('IRequest', 'IResponse', 'IResult', 'IHeld', 'IDebugFlags',
+    'IWSGIApplication')
 
 
 class IRequest(IExtendedReadMapping):
@@ -62,13 +63,6 @@
         """IPrincipal object associated with the request.
         """)
 
-    def retry():
-        """Returns a re-initialized request to be retried.
-
-        Returns a request suitable for repeating the publication attempt,
-        or raises RetryNotSupported if the response can not be retried.
-        """
-
     bodyStream = Attribute(
         """The stream that provides the data of the request.
 
@@ -204,17 +198,10 @@
     def reset():
         """Reset the output result.
 
-        Reset the response by nullifying already set variables.
+        Reset the response by clearing the status, headers, and body.
         """
 
-    def retry():
-        """Returns a re-initialized response to be retried.
 
-        Returns a response suitable for repeating the publication attempt,
-        or raises RetryNotSupported if the response can not be retried.
-        """
-
-
 class IResult(Interface):
     """An iterable that provides the body data of the response.
 
@@ -267,3 +254,14 @@
 
     sourceAnnotations = Attribute("""Enable ZPT source annotations""")
     showTAL = Attribute("""Leave TAL markup in rendered page templates""")
+
+
+class IWSGIApplication(Interface):
+    """Implements the WSGI application spec.  See PEP 333:
+
+    http://www.python.org/dev/peps/pep-0333/
+    """
+
+    def __call__(environ, start_response):
+        """Call the application and return a body iterator."""
+

Modified: Sandbox/shane/republish/zope/publisher/interfaces/http.py
===================================================================
--- Sandbox/shane/republish/zope/publisher/interfaces/http.py	2009-02-10 23:52:56 UTC (rev 96433)
+++ Sandbox/shane/republish/zope/publisher/interfaces/http.py	2009-02-11 02:02:48 UTC (rev 96434)
@@ -51,7 +51,7 @@
     method = Attribute("Request method, normalized to upper case")
 
     def setPathSuffix(steps):
-        """Deprecated: Add to traversal_stack instead.
+        """Deprecated: Prepend to traversal_stack instead.
         """
 
     locale = Attribute(



More information about the Checkins mailing list