Commit 0da54750 authored by Andreas Jung's avatar Andreas Jung

- Launchpad #374719: introducing new ZPublisher events:
  PubStart, PubSuccess, PubFailure, PubAfterTraversal and PubBeforeCommit
parent 63ac97bf
......@@ -23,6 +23,9 @@ Restructuring
Features Added
++++++++++++++
- Launchpad #374719: introducing new ZPublisher events:
PubStart, PubSuccess, PubFailure, PubAfterTraversal and PubBeforeCommit
- Launchpad #373583: ZODBMountPoint - fixed broken mount support and
extended the test suite.
......
......@@ -24,6 +24,10 @@ from zExceptions import Redirect
from zope.publisher.interfaces import ISkinnable
from zope.publisher.skinnable import setDefaultSkin
from zope.security.management import newInteraction, endInteraction
from zope.event import notify
from pubevents import PubStart, PubSuccess, PubFailure, \
PubBeforeCommit, PubAfterTraversal
class Retry(Exception):
"""Raise this to retry a request
......@@ -76,6 +80,7 @@ def publish(request, module_name, after_list, debug=0,
response=None
try:
notify(PubStart(request))
# TODO pass request here once BaseRequest implements IParticipation
newInteraction()
......@@ -110,6 +115,8 @@ def publish(request, module_name, after_list, debug=0,
object=request.traverse(path, validated_hook=validated_hook)
notify(PubAfterTraversal(request))
if transactions_manager:
transactions_manager.recordMetaData(object, request)
......@@ -122,12 +129,18 @@ def publish(request, module_name, after_list, debug=0,
if result is not response:
response.setBody(result)
notify(PubBeforeCommit(request))
if transactions_manager:
transactions_manager.commit()
endInteraction()
notify(PubSuccess(request))
return response
except:
# save in order to give 'PubFailure' the original exception info
exc_info = sys.exc_info()
# DM: provide nicer error message for FTP
sm = None
if response is not None:
......@@ -141,6 +154,7 @@ def publish(request, module_name, after_list, debug=0,
debug_mode and compact_traceback()[-1] or ''))
if err_hook is not None:
retry = False
if parents:
parents=parents[0]
try:
......@@ -157,10 +171,15 @@ def publish(request, module_name, after_list, debug=0,
sys.exc_info()[1],
sys.exc_info()[2],
)
retry = True
finally:
if transactions_manager:
transactions_manager.abort()
endInteraction()
# Note: 'abort's can fail. Nevertheless, we want end request handling
try:
if transactions_manager:
transactions_manager.abort()
finally:
endInteraction()
notify(PubFailure(request, exc_info, retry))
# Only reachable if Retry is raised and request supports retry.
newrequest=request.retry()
......@@ -175,9 +194,13 @@ def publish(request, module_name, after_list, debug=0,
newrequest.close()
else:
if transactions_manager:
transactions_manager.abort()
endInteraction()
# Note: 'abort's can fail. Nevertheless, we want end request handling
try:
if transactions_manager:
transactions_manager.abort()
finally:
endInteraction()
notify(PubFailure(request, exc_info, False))
raise
......
from zope.interface import Interface, Attribute
#############################################################################
# Publication events
# These are events notified in 'ZPublisher.Publish.publish'.
class IPubEvent(Interface):
'''Base class for publication events.
Publication events are notified in 'ZPublisher.Publish.publish' to
inform about publications (aka requests) and their fate.
'''
request = Attribute('The request being affected')
class IPubStart(IPubEvent):
'''Event notified at the beginning of 'ZPublisher.Publish.publish'.'''
class IPubEnd(IPubEvent):
'''Event notified after request processing.
Note that a retried request ends before the retrieal, the retrial
itself is considered a new event.
'''
class IPubSuccess(IPubEnd):
'''A successful request processing.'''
class IPubFailure(IPubEnd):
'''A failed request processing.
Note: If a subscriber to 'IPubSuccess' raises an exception,
then 'IPubFailure' may be notified in addtion to 'IPubSuccess'.
'''
exc_info = Attribute('''The exception info as returned by 'sys.exc_info()'.''')
retry = Attribute('Whether the request will be retried')
class IPubAfterTraversal(IPubEvent):
"""notified after traversal and an (optional) authentication."""
class IPubBeforeCommit(IPubEvent):
"""notified immediately before the transaction commit (i.e. after the main
request processing is finished.
"""
'''Publication events.
They are notified in 'ZPublisher.Publish.publish' and
inform about publications and their fate.
Subscriptions can be used for all kinds of request supervision,
e.g. request and error rate determination, writing high resolution logfiles
for detailed time related analysis, inline request monitoring.
'''
from zope.interface import implements
from interfaces import IPubStart, IPubSuccess, IPubFailure, \
IPubAfterTraversal, IPubBeforeCommit
class _Base(object):
"""PubEvent base class."""
def __init__(self, request):
self.request = request
class PubStart(_Base):
'''notified at the beginning of 'ZPublisher.Publish.publish'.'''
implements(IPubStart)
class PubSuccess(_Base):
'''notified at successful request end.'''
implements(IPubSuccess)
class PubFailure(object):
'''notified at failed request end.'''
implements(IPubFailure)
def __init__(self, request, exc_info, retry):
self.request, self.exc_info, self.retry = request, exc_info, retry
class PubAfterTraversal(_Base):
"""notified after traversal and an (optional) authentication."""
implements(IPubAfterTraversal)
class PubBeforeCommit(_Base):
"""notified immediately before the commit."""
implements(IPubBeforeCommit)
from sys import modules, exc_info
from unittest import TestCase, TestSuite, makeSuite, main
from ZODB.POSException import ConflictError
from zope.interface.verify import verifyObject
from zope.event import subscribers
from ZPublisher.Publish import publish, Retry
from ZPublisher.BaseRequest import BaseRequest
from ZPublisher.pubevents import PubStart, PubSuccess, PubFailure, \
PubAfterTraversal, PubBeforeCommit
from ZPublisher.interfaces import \
IPubStart, IPubEnd, IPubSuccess, IPubFailure, \
IPubAfterTraversal, IPubBeforeCommit
PUBMODULE = 'TEST_testpubevents'
_g=globals()
class TestInterface(TestCase):
def testPubStart(self):
verifyObject(IPubStart, PubStart(_Request()))
def testPubSuccess(self):
e = PubSuccess(_Request())
verifyObject(IPubSuccess, e)
verifyObject(IPubEnd, e)
def testPubFailure(self):
# get some exc info
try: raise ValueError()
except: exc = exc_info()
e = PubFailure(_Request(), exc, False)
verifyObject(IPubFailure, e)
verifyObject(IPubEnd, e)
def testAfterTraversal(self):
e = PubAfterTraversal(_Request())
verifyObject(IPubAfterTraversal, e)
def testBeforeCommit(self):
e = PubBeforeCommit(_Request())
verifyObject(IPubBeforeCommit, e)
class TestPubEvents(TestCase):
def setUp(self):
self._saved_subscribers = subscribers[:]
self.reporter = r = _Reporter()
subscribers[:] = [r]
modules[PUBMODULE] = __import__(__name__, _g, _g, ('__doc__', ))
self.request = _Request()
def tearDown(self):
if PUBMODULE in modules: del modules[PUBMODULE]
subscribers[:] = self._saved_subscribers
def testSuccess(self):
r = self.request; r.action = 'succeed'
publish(r, PUBMODULE, [None])
events = self.reporter.events
self.assertEqual(len(events), 4)
self.assert_(isinstance(events[0], PubStart))
self.assertEqual(events[0].request, r)
self.assert_(isinstance(events[-1], PubSuccess))
self.assertEqual(events[-1].request, r)
# test AfterTraversal and BeforeCommit as well
self.assert_(isinstance(events[1], PubAfterTraversal))
self.assertEqual(events[1].request, r)
self.assert_(isinstance(events[2], PubBeforeCommit))
self.assertEqual(events[2].request, r)
def testFailureReturn(self):
r = self.request; r.action = 'fail_return'
publish(r, PUBMODULE, [None])
events = self.reporter.events
self.assertEqual(len(events), 2)
self.assert_(isinstance(events[0], PubStart))
self.assertEqual(events[0].request, r)
self.assert_(isinstance(events[1], PubFailure))
self.assertEqual(events[1].request, r)
self.assertEqual(events[1].retry, False)
self.assertEqual(len(events[1].exc_info), 3)
def testFailureException(self):
r = self.request; r.action = 'fail_exception'
self.assertRaises(Exception, publish, r, PUBMODULE, [None])
events = self.reporter.events
self.assertEqual(len(events), 2)
self.assert_(isinstance(events[0], PubStart))
self.assertEqual(events[0].request, r)
self.assert_(isinstance(events[1], PubFailure))
self.assertEqual(events[1].request, r)
self.assertEqual(events[1].retry, False)
self.assertEqual(len(events[1].exc_info), 3)
def testFailureConflict(self):
r = self.request; r.action = 'conflict'
publish(r, PUBMODULE, [None])
events = self.reporter.events
self.assertEqual(len(events), 6)
self.assert_(isinstance(events[0], PubStart))
self.assertEqual(events[0].request, r)
self.assert_(isinstance(events[1], PubFailure))
self.assertEqual(events[1].request, r)
self.assertEqual(events[1].retry, True)
self.assertEqual(len(events[1].exc_info), 3)
self.assert_(isinstance(events[1].exc_info[1], ConflictError))
self.assert_(isinstance(events[2], PubStart))
self.assert_(isinstance(events[5], PubSuccess))
# Auxiliaries
def _succeed():
''' '''
return 'success'
class _Application(object): pass
class _Reporter(object):
def __init__(self): self.events = []
def __call__(self, event): self.events.append(event)
class _Response(object):
def setBody(*unused): pass
class _Request(BaseRequest):
response = _Response()
_hacked_path = False
args = ()
def __init__(self, *args, **kw):
BaseRequest.__init__(self, *args, **kw)
self['PATH_INFO'] = self['URL'] = ''
self.steps = []
def supports_retry(self): return True
def retry(self):
r = self.__class__()
r.action = 'succeed'
return r
def traverse(self, *unused, **unused_kw):
action = self.action
if action.startswith('fail'): raise Exception(action)
if action == 'conflict': raise ConflictError()
if action == 'succeed': return _succeed
else: raise ValueError('unknown action: %s' % action)
# override to get rid of the 'EndRequestEvent' notification
def close(self): pass
# define things necessary for publication
bobo_application = _Application()
def zpublisher_exception_hook(parent, request, *unused):
action = request.action
if action == 'fail_return': return 0
if action == 'fail_exception': raise Exception()
if action == 'conflict': raise Retry()
raise ValueError('unknown action: %s' % action)
def test_suite():
return TestSuite((makeSuite(c) for c in (TestPubEvents, TestInterface)))
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment