##// END OF EJS Templates
I.k.c.notification blueprint, test, implementation.
Barry Wark -
Show More
@@ -0,0 +1,85 b''
1 # encoding: utf-8
2
3 """The IPython Core Notification Center.
4
5 See docs/blueprints/notification_blueprint.txt for an overview of the
6 notification module.
7 """
8
9 __docformat__ = "restructuredtext en"
10
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
17
18
19 class NotificationCenter(object):
20 """Synchronous notification center"""
21 def __init__(self):
22 super(NotificationCenter, self).__init__()
23 self.registeredTypes = set() #set of types that are observed
24 self.registeredSenders = set() #set of senders that are observed
25 self.observers = {} #map (type,sender) => callback (callable)
26
27
28 def post_notification(self, theType, sender, **kwargs):
29 """Post notification (type,sender,**kwargs) to all registered
30 observers.
31
32 Implementation
33 --------------
34 * If no registered observers, performance is O(1).
35 * Notificaiton order is undefined.
36 * Notifications are posted synchronously.
37 """
38
39 if(theType==None or sender==None):
40 raise Exception("NotificationCenter.post_notification requires \
41 type and sender.")
42
43 # If there are no registered observers for the type/sender pair
44 if((theType not in self.registeredTypes) or
45 (sender not in self.registeredSenders)):
46 return
47
48 keys = ((theType,sender), (None, sender), (theType, None), (None,None))
49 observers = set()
50 for k in keys:
51 observers.update(self.observers.get(k, set()))
52
53 for o in observers:
54 o(theType, sender, args=kwargs)
55
56
57 def add_observer(self, observerCallback, theType, sender):
58 """Add an observer callback to this notification center.
59
60 The given callback will be called upon posting of notifications of
61 the given type/sender and will receive any additional kwargs passed
62 to post_notification.
63
64 Parameters
65 ----------
66 observerCallback : callable
67 Callable. Must take at least two arguments::
68 observerCallback(type, sender, args={})
69
70 theType : hashable
71 The notification type. If None, all notifications from sender
72 will be posted.
73
74 sender : hashable
75 The notification sender. If None, all notifications of theType
76 will be posted.
77 """
78 assert(observerCallback != None)
79 self.registeredTypes.add(theType)
80 self.registeredSenders.add(sender)
81 self.observers.setdefault((theType,sender), set()).add(observerCallback)
82
83
84
85 sharedCenter = NotificationCenter() No newline at end of file
@@ -0,0 +1,118 b''
1 # encoding: utf-8
2
3 """This file contains unittests for the notification.py module."""
4
5 __docformat__ = "restructuredtext en"
6
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17
18 from IPython.kernel.core.notification import NotificationCenter,\
19 sharedCenter
20
21 #
22 # Supporting test classes
23 #
24
25 class Observer(object):
26 """docstring for Observer"""
27 def __init__(self, expectedType, expectedSender, **kwargs):
28 super(Observer, self).__init__()
29 self.expectedType = expectedType
30 self.expectedSender = expectedSender
31 self.expectedKwArgs = kwargs
32 self.recieved = False
33 sharedCenter.add_observer(self.callback,
34 self.expectedType,
35 self.expectedSender)
36
37
38 def callback(self, theType, sender, args={}):
39 """callback"""
40
41 assert(theType == self.expectedType)
42 assert(sender == self.expectedSender)
43 assert(args == self.expectedKwArgs)
44 self.recieved = True
45
46
47 def verify(self):
48 """verify"""
49
50 assert(self.recieved)
51
52
53
54 class Notifier(object):
55 """docstring for Notifier"""
56 def __init__(self, theType, **kwargs):
57 super(Notifier, self).__init__()
58 self.theType = theType
59 self.kwargs = kwargs
60
61 def post(self, center=sharedCenter):
62 """fire"""
63
64 center.post_notification(self.theType, self,
65 **self.kwargs)
66
67
68 #
69 # Test Cases
70 #
71
72
73 def test_notification_delivered():
74 """Test that notifications are delivered"""
75 expectedType = 'EXPECTED_TYPE'
76 sender = Notifier(expectedType)
77 observer = Observer(expectedType, sender)
78
79 sender.post()
80
81 observer.verify()
82
83
84 def test_type_specificity():
85 """Test that observers are registered by type"""
86
87 expectedType = 1
88 unexpectedType = "UNEXPECTED_TYPE"
89 sender = Notifier(expectedType)
90 unexpectedSender = Notifier(unexpectedType)
91 observer = Observer(expectedType, sender)
92
93 sender.post()
94 unexpectedSender.post()
95
96 observer.verify()
97
98
99 def test_sender_specificity():
100 """Test that observers are registered by sender"""
101
102 expectedType = "EXPECTED_TYPE"
103 sender1 = Notifier(expectedType)
104 sender2 = Notifier(expectedType)
105 observer = Observer(expectedType, sender1)
106
107 sender1.post()
108 sender2.post()
109
110 observer.verify()
111
112
113 def test_complexity_with_no_observers():
114 """Test that the notification center's algorithmic complexity is O(1)
115 with no registered observers (for the given notification type)
116 """
117
118 assert(False) #I'm not sure how to test this yet
@@ -0,0 +1,47 b''
1 .. Notification:
2
3 ==========================================
4 IPython.kernel.core.notification blueprint
5 ==========================================
6
7 Overview
8 ========
9 The IPython.kernel.core.notification module will provide a simple implementation of a notification center and support for the observer pattern within the IPython.kernel.core. The main intended use case is to provide notification of Interpreter events to an observing frontend during the execution of a single block of code.
10
11 Functional Requirements
12 =======================
13 The notification center must:
14 * Provide synchronous notification of events to all registered observers.
15 * Provide typed or labeled notification types
16 * Allow observers to register callbacks for individual or all notification types
17 * Allow observers to register callbacks for events from individual or all notifying objects
18 * Notification to the observer consists of the notification type, notifying object and user-supplied extra information [implementation: as keyword parameters to the registered callback]
19 * Perform as O(1) in the case of no registered observers.
20 * Permit out-of-process or cross-network extension.
21
22 What's not included
23 ==============================================================
24 As written, the IPython.kernel.core.notificaiton module does not:
25 * Provide out-of-process or network notifications [these should be handled by a separate, Twisted aware module in IPython.kernel].
26 * Provide zope.interface-style interfaces for the notification system [these should also be provided by the IPython.kernel module]
27
28 Use Cases
29 =========
30 The following use cases describe the main intended uses of the notificaiton module and illustrate the main success scenario for each use case:
31
32 1. Dwight Schroot is writing a frontend for the IPython project. His frontend is stuck in the stone age and must communicate synchronously with an IPython.kernel.core.Interpreter instance. Because code is executed in blocks by the Interpreter, Dwight's UI freezes every time he executes a long block of code. To keep track of the progress of his long running block, Dwight adds the following code to his frontend's set-up code::
33 from IPython.kernel.core.notification import NotificationCenter
34 center = NotificationCenter.sharedNotificationCenter
35 center.registerObserver(self, type=IPython.kernel.core.Interpreter.STDOUT_NOTIFICATION_TYPE, notifying_object=self.interpreter, callback=self.stdout_notification)
36
37 and elsewhere in his front end::
38 def stdout_notification(self, type, notifying_object, out_string=None):
39 self.writeStdOut(out_string)
40
41 If everything works, the Interpreter will (according to its published API) fire a notification via the sharedNotificationCenter of type STD_OUT_NOTIFICAIONT_TYPE before writing anything to stdout [it's up to the Intereter implementation to figure out when to do this]. The notificaiton center will then call the registered callbacks for that event type (in this case, Dwight's frontend's stdout_notification method). Again, according to its API, the Interpreter provides an additional keyword argument when firing the notificaiton of out_string, a copy of the string it will write to stdout.
42
43 Like magic, Dwight's frontend is able to provide output, even during long-running calculations. Now if Jim could just convince Dwight to use Twisted...
44
45 2. Boss Hog is writing a frontend for the IPython project. Because Boss Hog is stuck in the stone age, his frontend will be written in a new Fortran-like dialect of python and will run only from the command line. Because he doesn't need any fancy notification system and is used to worrying about every cycle on his rat-wheel powered mini, Boss Hog is adamant that the new notification system not produce any performance penalty. As they say in Hazard county, there's no such thing as a free lunch. If he wanted zero overhead, he should have kept using IPython 0.8. Instead, those tricky Duke boys slide in a suped-up bridge-out jumpin' awkwardly confederate-lovin' notification module that imparts only a constant (and small) performance penalty when the Interpreter (or any other object) fires an event for which there are no registered observers. Of course, the same notificaiton-enabled Interpreter can then be used in frontends that require notifications, thus saving the IPython project from a nasty civil war.
46
47 3. Barry is wrting a frontend for the IPython project. Because Barry's front end is the *new hotness*, it uses an asynchronous event model to communicate with a Twisted engineservice that communicates with the IPython Interpreter. Using the IPython.kernel.notification module, an asynchronous wrapper on the IPython.kernel.core.notification module, Barry's frontend can register for notifications from the interpreter that are delivered asynchronously. Even if Barry's frontend is running on a separate process or even host from the Interpreter, the notifications are delivered, as if by dark and twisted magic. Just like Dwight's frontend, Barry's frontend can now recieve notifications of e.g. writing to stdout/stderr, opening/closing an external file, an exception in the executing code, etc. No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now