[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