[Zope-Checkins] SVN: Zope/branches/haufe-legacy-integration/ - Launchpad #374719: introducing new ZPublisher events:

Andreas Jung andreas at andreas-jung.com
Mon May 11 04:07:26 EDT 2009


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)))
+
+        
+



More information about the Zope-Checkins mailing list