Log message for revision 99835: - Launchpad #374719: introducing new ZPublisher events: PubStart, PubSuccess, PubFailure, PubAfterTraversal and PubBeforeCommit
Changed: U Zope/branches/haufe-legacy-integration/doc/CHANGES.rst U Zope/branches/haufe-legacy-integration/src/ZPublisher/Publish.py A Zope/branches/haufe-legacy-integration/src/ZPublisher/interfaces.py A Zope/branches/haufe-legacy-integration/src/ZPublisher/pubevents.py A Zope/branches/haufe-legacy-integration/src/ZPublisher/tests/testpubevents.py -=- Modified: Zope/branches/haufe-legacy-integration/doc/CHANGES.rst =================================================================== --- Zope/branches/haufe-legacy-integration/doc/CHANGES.rst 2009-05-11 06:55:54 UTC (rev 99834) +++ Zope/branches/haufe-legacy-integration/doc/CHANGES.rst 2009-05-11 08:07:25 UTC (rev 99835) @@ -23,6 +23,9 @@ 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. Modified: Zope/branches/haufe-legacy-integration/src/ZPublisher/Publish.py =================================================================== --- Zope/branches/haufe-legacy-integration/src/ZPublisher/Publish.py 2009-05-11 06:55:54 UTC (rev 99834) +++ Zope/branches/haufe-legacy-integration/src/ZPublisher/Publish.py 2009-05-11 08:07:25 UTC (rev 99835) @@ -24,7 +24,11 @@ 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 @@ response=None try: + notify(PubStart(request)) # TODO pass request here once BaseRequest implements IParticipation newInteraction() @@ -110,6 +115,8 @@ object=request.traverse(path, validated_hook=validated_hook) + notify(PubAfterTraversal(request)) + if transactions_manager: transactions_manager.recordMetaData(object, request) @@ -122,12 +129,18 @@ 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 @@ 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 @@ 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 @@ 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 Added: Zope/branches/haufe-legacy-integration/src/ZPublisher/interfaces.py =================================================================== --- Zope/branches/haufe-legacy-integration/src/ZPublisher/interfaces.py (rev 0) +++ Zope/branches/haufe-legacy-integration/src/ZPublisher/interfaces.py 2009-05-11 08:07:25 UTC (rev 99835) @@ -0,0 +1,45 @@ +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. + """ Added: Zope/branches/haufe-legacy-integration/src/ZPublisher/pubevents.py =================================================================== --- Zope/branches/haufe-legacy-integration/src/ZPublisher/pubevents.py (rev 0) +++ Zope/branches/haufe-legacy-integration/src/ZPublisher/pubevents.py 2009-05-11 08:07:25 UTC (rev 99835) @@ -0,0 +1,44 @@ +'''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) Added: Zope/branches/haufe-legacy-integration/src/ZPublisher/tests/testpubevents.py =================================================================== --- Zope/branches/haufe-legacy-integration/src/ZPublisher/tests/testpubevents.py (rev 0) +++ Zope/branches/haufe-legacy-integration/src/ZPublisher/tests/testpubevents.py 2009-05-11 08:07:25 UTC (rev 99835) @@ -0,0 +1,166 @@ +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))) + + + _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org http://mail.zope.org/mailman/listinfo/zope-checkins