[Checkins] SVN: Sandbox/shane/republish/zope/p sketched more pipeline stages

Shane Hathaway shane at hathawaymix.org
Wed Feb 11 00:28:00 EST 2009


Log message for revision 96435:
  sketched more pipeline stages
  

Changed:
  A   Sandbox/shane/republish/zope/pipeline/authenticator.py
  U   Sandbox/shane/republish/zope/pipeline/autotemp.py
  U   Sandbox/shane/republish/zope/pipeline/build.py
  U   Sandbox/shane/republish/zope/pipeline/caller.py
  U   Sandbox/shane/republish/zope/pipeline/configure.zcml
  A   Sandbox/shane/republish/zope/pipeline/event.py
  U   Sandbox/shane/republish/zope/pipeline/retry.py
  A   Sandbox/shane/republish/zope/pipeline/rootopen.py
  D   Sandbox/shane/republish/zope/pipeline/traversalroot.py
  A   Sandbox/shane/republish/zope/pipeline/txnctl.py
  D   Sandbox/shane/republish/zope/pipeline/txnmiddle.py
  U   Sandbox/shane/republish/zope/publisher/interfaces/base.py

-=-
Added: Sandbox/shane/republish/zope/pipeline/authenticator.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/authenticator.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/authenticator.py	2009-02-11 05:27:59 UTC (rev 96435)
@@ -0,0 +1,70 @@
+
+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
+from zope.security.management import endInteraction
+
+from zope.app.security.interfaces import IAuthentication
+from zope.app.security.interfaces import IFallbackUnauthenticatedPrincipal
+
+
+class Authenticator(object):
+    """WSGI app that hooks into Zope-based authentication.
+
+    The WSGI environment must contain 'zope.request'.
+    """
+    implements(IWSGIApplication)
+    adapts(IWSGIApplication)
+
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        request = environ['zope.request']
+        auth = getGlobalSiteManager().getUtility(IAuthentication)
+        principal = auth.authenticate(request)
+        if principal is None:
+            request.traversal_hooks.append(placeful_auth)
+            principal = auth.unauthenticatedPrincipal()
+            if principal is None:
+                # Get the fallback unauthenticated principal
+                principal = getUtility(IFallbackUnauthenticatedPrincipal)
+        request.principal = principal
+
+        newInteraction(request)
+        try:
+            return self.app(environ, start_response)
+        finally:
+            endInteraction()
+
+
+def placeful_auth(request, ob):
+    """Traversal hook that tries to authenticate in a context"""
+
+    if not IUnauthenticatedPrincipal.providedBy(request.principal):
+        # We've already got an authenticated user. There's nothing to do.
+        # Note that beforeTraversal guarentees that user is not None.
+        return
+
+    if not ISite.providedBy(ob):
+        # We won't find an authentication utility here, so give up.
+        return
+
+    sm = removeSecurityProxy(ob).getSiteManager()
+
+    auth = sm.queryUtility(IAuthentication)
+    if auth is None:
+        # No auth utility here
+        return
+
+    # Try to authenticate against the auth utility
+    principal = auth.authenticate(request)
+    if principal is None:
+        principal = auth.unauthenticatedPrincipal()
+        if principal is None:
+            # nothing to do here
+            return
+
+    request.setPrincipal(principal)

Modified: Sandbox/shane/republish/zope/pipeline/autotemp.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/autotemp.py	2009-02-11 02:02:48 UTC (rev 96434)
+++ Sandbox/shane/republish/zope/pipeline/autotemp.py	2009-02-11 05:27:59 UTC (rev 96435)
@@ -8,23 +8,26 @@
     def __init__(self, threshold=bufsize):
         self._threshold = threshold
         self._f = f = StringIO()
+        self._switched = False
         # 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:
+        if not self._switched and self.tell() + len(data) >= self._threshold:
             # convert to TemporaryFile
             f = tempfile.TemporaryFile()
             f.write(self._f.getvalue())
             f.seek(self.tell())
             self._f = f
+            self._switched = True
             # 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):
+        """Fill this file with the contents of the given file"""
         while True:
             data = src.read(bufsize)
             if not data:
@@ -32,6 +35,7 @@
             self.write(data)
 
     def copyto(self, dest):
+        """Send the contents of this file to the given file"""
         while True:
             data = self.read(bufsize)
             if not data:

Modified: Sandbox/shane/republish/zope/pipeline/build.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/build.py	2009-02-11 02:02:48 UTC (rev 96434)
+++ Sandbox/shane/republish/zope/pipeline/build.py	2009-02-11 05:27:59 UTC (rev 96435)
@@ -6,29 +6,23 @@
 
 from zope.publisher.interfaces import IWSGIApplication
 
-standard_stages = []  # set by pipeline:stages directive
+configured_stages = []  # set by pipeline:stages directive
 
-def make_app(configuration, stages=standard_stages):
+def make_app(configuration, stages=configured_stages):
     stages = list(stages)  # make a copy
+
+    # the last stage name is the application
     name = stages.pop()
-    app = build_stage(None, name, configuration)
+    app = IWSGIApplication(configuration, name=name, default=None)
+    if app is None:
+        app = IWSGIApplication(name=name)
+
+    # the rest are middleware
     while stages:
         name = stages.pop()
-        app = build_stage(app, name, configuration)
-    return app
+        new_app = IWSGIApplication(app, configuration, name=name, default=None)
+        if new_app is None:
+            new_app = IWSGIApplication(app, name=name)
+        app = new_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
+    return app

Modified: Sandbox/shane/republish/zope/pipeline/caller.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/caller.py	2009-02-11 02:02:48 UTC (rev 96434)
+++ Sandbox/shane/republish/zope/pipeline/caller.py	2009-02-11 05:27:59 UTC (rev 96435)
@@ -1,5 +1,7 @@
 
+from zope.interface import implements
 from zope.proxy import removeAllProxies
+from zope.publisher.interfaces import IWSGIApplication
 
 class Caller(object):
     """WSGI app that calls the traversed object.
@@ -7,10 +9,12 @@
     Requires 'zope.request', which implements IRequest, in the environment.
     The 'traversed' attribute of the request must be set.
     """
+    implements(IWSGIApplication)
+
     def __call__(self, environ, start_response):
         request = environ['zope.request']
         name, ob = request.traversed[-1]
-        result = mapply(ob, request.getPositionalArguments(), request)
+        result = mapply(ob, request.positional_arguments, request)
         response = request.response
         if result is not response:
             response.setResult(result)

Modified: Sandbox/shane/republish/zope/pipeline/configure.zcml
===================================================================
--- Sandbox/shane/republish/zope/pipeline/configure.zcml	2009-02-11 02:02:48 UTC (rev 96434)
+++ Sandbox/shane/republish/zope/pipeline/configure.zcml	2009-02-11 05:27:59 UTC (rev 96435)
@@ -9,12 +9,12 @@
     # DetailedTracebackGenerator
 
     # stages we're using:
-    'request_logger',
+    'logger',
     'retry',
-    'request_creator',        # also sets locale (?)
-    'traversal_root_opener',
+    'request_factory',        # also sets locale (?)
+    'root_opener',
     'transaction_controller',
-    'app_error_handler',
+    'error_handler',
     'global_authenticator',
     # Note: the traverser also has to invoke authentication at each step.
     'traverser',
@@ -23,22 +23,59 @@
 -->
 
 <pipeline:stages names="
-    request_logger
+    logger
     retry
-    request_creator
-    traversal_root_opener
+    request_factory
+    root_opener
     transaction_controller
-    app_error_handler
-    global_authenticator
+    notifier
+    error_handler
+    authenticator
     traverser
     transaction_annotator
     caller
     " />
 
-<zope:adapter
-    factory=".txnmiddle.TransactionController"
+<adapter
+    factory="..."
+    name="logger" />
+
+<adapter
+    factory=".retry.Retry"
+    name="retry" />
+
+<adapter
+    factory="..."
+    name="request_factory" />
+
+<adapter
+    factory=".rootopen.RootOpener"
+    name="root_opener" />
+
+<adapter
+    factory=".txnctl.TransactionController"
     name="transaction_controller" />
 
-<zope:adapter
-    factory=".txnmiddle.TransactionAnnotator"
+<adapter
+    factory=".event.EventNotifier"
+    name="notifier" />
+
+<adapter
+    factory="..."
+    name="error_handler" />
+
+<adapter
+    factory=".authenticator.Authenticator"
+    name="authenticator" />
+
+<adapter
+    factory="..."
+    name="traverser" />
+
+<adapter
+    factory=".txnctl.TransactionAnnotator"
     name="transaction_annotator" />
+
+<adapter
+    factory=".caller.Caller"
+    name="caller" />

Added: Sandbox/shane/republish/zope/pipeline/event.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/event.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/event.py	2009-02-11 05:27:59 UTC (rev 96435)
@@ -0,0 +1,35 @@
+
+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
+from zope.publisher.interfaces.event import EndRequestEvent
+
+
+class EventNotifier(object):
+    """Fires request-related events.
+
+    Fires are BeforeTraverseEvent and EndRequestEvent at the appropriate
+    times.
+    """
+    implements(IWSGIApplication)
+    adapts(IWSGIApplication)
+
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        request = environ['zope.request']
+        request.traversal_hooks.append(self.fireBeforeTraverse)
+        try:
+            return self.app(environ, start_response)
+        finally:
+            if request.traversed:
+                name, ob = request.traversed[-1]
+            else:
+                ob = None
+            notify(EndRequestEvent(ob, request))
+
+    def fireBeforeTraverse(self, request, ob):
+        notify(BeforeTraverseEvent(ob, request))

Modified: Sandbox/shane/republish/zope/pipeline/retry.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/retry.py	2009-02-11 02:02:48 UTC (rev 96434)
+++ Sandbox/shane/republish/zope/pipeline/retry.py	2009-02-11 05:27:59 UTC (rev 96435)
@@ -1,4 +1,6 @@
 
+from zope.interface import adapts
+from zope.interface import implements
 from zope.publisher.interfaces import IWSGIApplication
 from zope.publisher.interfaces.exceptions import Retry
 from ZODB.POSException import ConflictError
@@ -11,8 +13,8 @@
 
     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.
+    should propagate Retry or ConflictError when 'zope.can_retry' is
+    true.
     """
     implements(IWSGIApplication)
     adapts(IWSGIApplication)
@@ -28,6 +30,7 @@
                 # make the input stream rewindable
                 f = AutoTemporaryFile()
                 f.copyfrom(wsgi_input)
+                f.seek(0)
                 environ['wsgi.input'] = wsgi_input = f
 
         def retryable_start_response(status, response_headers, exc_info=None):
@@ -43,6 +46,13 @@
             environ['zope.can_retry'] = True
             try:
                 res = self.app(environ, retryable_start_response)
+            except (Retry, ConflictError):
+                if 'zope.request' in environ:
+                    del environ['zope.request']
+                if wsgi_input is not None:
+                    wsgi_input.seek(0)
+                attempt += 1
+            else:
                 if start_response_params:
                     dest = start_response(*tuple(start_response_params))
                     src = output_file[0]
@@ -50,12 +60,6 @@
                     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

Copied: Sandbox/shane/republish/zope/pipeline/rootopen.py (from rev 96434, Sandbox/shane/republish/zope/pipeline/traversalroot.py)
===================================================================
--- Sandbox/shane/republish/zope/pipeline/rootopen.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/rootopen.py	2009-02-11 05:27:59 UTC (rev 96435)
@@ -0,0 +1,55 @@
+
+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
+
+
+class RootOpener(object):
+    """Puts a root object in 'zope.request' of the WSGI environment.
+
+    Sets request.traversed to a list with one element.
+    Also closes the database connection on the way out.
+
+    Special case: if the traversal stack contains "++etc++process",
+    instead of opening the database, this uses the utility by that
+    name as the root object.
+    """
+    implements(IWSGIApplication)
+    adapts(IWSGIApplication, IZopeConfiguration)
+
+    database_name = 'main'
+    root_name = 'Application'
+    app_controller_name = '++etc++process'
+
+    def __init__(self, app, zope_conf):
+        self.app = app
+        self.db = zope_conf.databases[self.database_name]
+
+    def __call__(self, environ, start_response):
+        request = environ['zope.request']
+
+        # If the traversal stack contains self.app_controller_name,
+        # then we should get the app controller rather than look
+        # in the database.
+        if self.app_controller_name in request.traversal_stack:
+            root = getUtility(name=self.app_controller_name)
+            request.traversed = [(self.app_controller_name, root)]
+            return self.app(environ, start_response)
+
+        # Open the database.
+        conn = self.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))]
+
+        try:
+            return self.app(environ, start_response)
+        finally:
+            conn.close()

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

Copied: Sandbox/shane/republish/zope/pipeline/txnctl.py (from rev 96434, Sandbox/shane/republish/zope/pipeline/txnmiddle.py)
===================================================================
--- Sandbox/shane/republish/zope/pipeline/txnctl.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/txnctl.py	2009-02-11 05:27:59 UTC (rev 96435)
@@ -0,0 +1,92 @@
+
+
+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
+from zope.publisher.interfaces import IWSGIApplication
+from zope.security.proxy import removeSecurityProxy
+
+
+class TransactionController(object):
+    """WSGI middleware that begins and commits/aborts 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

Deleted: Sandbox/shane/republish/zope/pipeline/txnmiddle.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/txnmiddle.py	2009-02-11 02:02:48 UTC (rev 96434)
+++ Sandbox/shane/republish/zope/pipeline/txnmiddle.py	2009-02-11 05:27:59 UTC (rev 96435)
@@ -1,92 +0,0 @@
-
-
-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-11 02:02:48 UTC (rev 96434)
+++ Sandbox/shane/republish/zope/publisher/interfaces/base.py	2009-02-11 05:27:59 UTC (rev 96435)
@@ -101,17 +101,11 @@
           "zope.persistentadapter"
         """)
 
-    traversed = Attribute(
-        """List of (name, obj) steps that were traversed.
-
-        The first object is the application root and has an empty name.
-        The last object is the object to call.
-        """)
-
     def getBasicCredentials():
-        """Return (login, password) if there are basic credentials.
+        """Return (login, password) if the request contains basic credentials.
 
-        Returns None if there aren't.
+        Returns None if no such credentials are in the request or the
+        credentials are of some other type.
         """
 
     def _authUserPW():
@@ -122,7 +116,25 @@
         """Deprecated: use response.unauthorized() instead.
         """
 
+    traversed = Attribute(
+        """List of (name, obj) steps that were traversed.
 
+        The first object is the application root and may have an empty name.
+        The last object is the object to call.
+        """)
+
+    traversal_hooks = Attribute(
+        """List of hooks to call before each traversal step.
+
+        Each hook will be called with two parameters, request and ob.
+        The hook does not need to return anything.
+
+        These hooks will be called before traversing an object for the
+        first time.  If the same object is traversed more than
+        once, the hook will still only be called the first time.
+        """
+
+
 class IResponse(Interface):
     """Holds a response result."""
 



More information about the Checkins mailing list