[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