Show More
@@ -271,6 +271,7 b' class CommandLineConfigLoader(ConfigLoader):' | |||
|
271 | 271 | class NoConfigDefault(object): pass |
|
272 | 272 | NoConfigDefault = NoConfigDefault() |
|
273 | 273 | |
|
274 | ||
|
274 | 275 | class ArgParseConfigLoader(CommandLineConfigLoader): |
|
275 | 276 | |
|
276 | 277 | # arguments = [(('-f','--file'),dict(type=str,dest='file'))] |
@@ -58,18 +58,29 b' class ClusterDir(Component):' | |||
|
58 | 58 | os.chmod(new, 0777) |
|
59 | 59 | self.security_dir = os.path.join(new, self.security_dir_name) |
|
60 | 60 | self.log_dir = os.path.join(new, self.log_dir_name) |
|
61 | self.check_dirs() | |
|
61 | 62 | |
|
62 | 63 | def _log_dir_changed(self, name, old, new): |
|
63 | if not os.path.isdir(new): | |
|
64 | os.mkdir(new, 0777) | |
|
64 | self.check_log_dir() | |
|
65 | ||
|
66 | def check_log_dir(self): | |
|
67 | if not os.path.isdir(self.log_dir): | |
|
68 | os.mkdir(self.log_dir, 0777) | |
|
65 | 69 | else: |
|
66 |
os.chmod( |
|
|
70 | os.chmod(self.log_dir, 0777) | |
|
67 | 71 | |
|
68 | 72 | def _security_dir_changed(self, name, old, new): |
|
69 | if not os.path.isdir(new): | |
|
70 | os.mkdir(new, 0700) | |
|
73 | self.check_security_dir() | |
|
74 | ||
|
75 | def check_security_dir(self): | |
|
76 | if not os.path.isdir(self.security_dir): | |
|
77 | os.mkdir(self.security_dir, 0700) | |
|
71 | 78 | else: |
|
72 |
os.chmod( |
|
|
79 | os.chmod(self.security_dir, 0700) | |
|
80 | ||
|
81 | def check_dirs(self): | |
|
82 | self.check_security_dir() | |
|
83 | self.check_log_dir() | |
|
73 | 84 | |
|
74 | 85 | def load_config_file(self, filename): |
|
75 | 86 | """Load a config file from the top level of the cluster dir. |
@@ -83,7 +94,7 b' class ClusterDir(Component):' | |||
|
83 | 94 | loader = PyFileConfigLoader(filename, self.location) |
|
84 | 95 | return loader.load_config() |
|
85 | 96 | |
|
86 | def copy_config_file(self, config_file, path=None): | |
|
97 | def copy_config_file(self, config_file, path=None, overwrite=False): | |
|
87 | 98 | """Copy a default config file into the active cluster directory. |
|
88 | 99 | |
|
89 | 100 | Default configuration files are kept in :mod:`IPython.config.default`. |
@@ -96,12 +107,14 b' class ClusterDir(Component):' | |||
|
96 | 107 | path = os.path.sep.join(path) |
|
97 | 108 | src = os.path.join(path, config_file) |
|
98 | 109 | dst = os.path.join(self.location, config_file) |
|
99 | shutil.copy(src, dst) | |
|
110 | if not os.path.isfile(dst) or overwrite: | |
|
111 | shutil.copy(src, dst) | |
|
100 | 112 | |
|
101 | def copy_all_config_files(self): | |
|
113 | def copy_all_config_files(self, path=None, overwrite=False): | |
|
102 | 114 | """Copy all config files into the active cluster directory.""" |
|
103 |
for f in ['ipcontroller_config.py', 'ipengine_config.py' |
|
|
104 | self.copy_config_file(f) | |
|
115 | for f in ['ipcontroller_config.py', 'ipengine_config.py', | |
|
116 | 'ipcluster_config.py']: | |
|
117 | self.copy_config_file(f, path=path, overwrite=overwrite) | |
|
105 | 118 | |
|
106 | 119 | @classmethod |
|
107 | 120 | def find_cluster_dir_by_profile(cls, path, profile='default'): |
@@ -245,7 +258,6 b' class ApplicationWithClusterDir(Application):' | |||
|
245 | 258 | # priority, this will always end up in the master_config. |
|
246 | 259 | self.default_config.Global.cluster_dir = self.cluster_dir |
|
247 | 260 | self.command_line_config.Global.cluster_dir = self.cluster_dir |
|
248 | self.log.info("Cluster directory set to: %s" % self.cluster_dir) | |
|
249 | 261 | |
|
250 | 262 | # Set the search path to the cluster directory |
|
251 | 263 | self.config_file_paths = (self.cluster_dir,) |
@@ -20,14 +20,21 b' import cPickle as pickle' | |||
|
20 | 20 | |
|
21 | 21 | from twisted.python import log, failure |
|
22 | 22 | from twisted.internet import defer |
|
23 | from twisted.internet.defer import inlineCallbacks, returnValue | |
|
23 | 24 | |
|
24 | 25 | from IPython.kernel.fcutil import find_furl |
|
25 | 26 | from IPython.kernel.enginefc import IFCEngine |
|
27 | from IPython.kernel.twistedutil import sleep_deferred | |
|
26 | 28 | |
|
27 | 29 | #------------------------------------------------------------------------------- |
|
28 | 30 | # The ClientConnector class |
|
29 | 31 | #------------------------------------------------------------------------------- |
|
30 | 32 | |
|
33 | ||
|
34 | class EngineConnectorError(Exception): | |
|
35 | pass | |
|
36 | ||
|
37 | ||
|
31 | 38 | class EngineConnector(object): |
|
32 | 39 | """Manage an engines connection to a controller. |
|
33 | 40 | |
@@ -38,8 +45,9 b' class EngineConnector(object):' | |||
|
38 | 45 | |
|
39 | 46 | def __init__(self, tub): |
|
40 | 47 | self.tub = tub |
|
41 | ||
|
42 |
def connect_to_controller(self, engine_service, furl_or_file |
|
|
48 | ||
|
49 | def connect_to_controller(self, engine_service, furl_or_file, | |
|
50 | delay=0.1, max_tries=10): | |
|
43 | 51 | """ |
|
44 | 52 | Make a connection to a controller specified by a furl. |
|
45 | 53 | |
@@ -48,34 +56,65 b' class EngineConnector(object):' | |||
|
48 | 56 | foolscap URL contains all the information needed to connect to the |
|
49 | 57 | controller, including the ip and port as well as any encryption and |
|
50 | 58 | authentication information needed for the connection. |
|
51 | ||
|
59 | ||
|
52 | 60 | After getting a reference to the controller, this method calls the |
|
53 | 61 | `register_engine` method of the controller to actually register the |
|
54 | 62 | engine. |
|
55 | ||
|
56 | :Parameters: | |
|
57 | engine_service : IEngineBase | |
|
58 | An instance of an `IEngineBase` implementer | |
|
59 | furl_or_file : str | |
|
60 | A furl or a filename containing a furl | |
|
63 | ||
|
64 | This method will try to connect to the controller multiple times with | |
|
65 | a delay in between. Each time the FURL file is read anew. | |
|
66 | ||
|
67 | Parameters | |
|
68 | __________ | |
|
69 | engine_service : IEngineBase | |
|
70 | An instance of an `IEngineBase` implementer | |
|
71 | furl_or_file : str | |
|
72 | A furl or a filename containing a furl | |
|
73 | delay : float | |
|
74 | The intial time to wait between connection attempts. Subsequent | |
|
75 | attempts have increasing delays. | |
|
76 | max_tries : int | |
|
77 | The maximum number of connection attempts. | |
|
61 | 78 | """ |
|
62 | 79 | if not self.tub.running: |
|
63 | 80 | self.tub.startService() |
|
64 | 81 | self.engine_service = engine_service |
|
65 | 82 | self.engine_reference = IFCEngine(self.engine_service) |
|
66 | try: | |
|
67 | self.furl = find_furl(furl_or_file) | |
|
68 | except ValueError: | |
|
69 | return defer.fail(failure.Failure()) | |
|
83 | ||
|
84 | d = self._try_to_connect(furl_or_file, delay, max_tries, attempt=0) | |
|
85 | return d | |
|
86 | ||
|
87 | @inlineCallbacks | |
|
88 | def _try_to_connect(self, furl_or_file, delay, max_tries, attempt): | |
|
89 | """Try to connect to the controller with retry logic.""" | |
|
90 | if attempt < max_tries: | |
|
91 | log.msg("Attempting to connect to controller [%r]: %s" % \ | |
|
92 | (attempt, furl_or_file)) | |
|
93 | try: | |
|
94 | self.furl = find_furl(furl_or_file) | |
|
95 | # Uncomment this to see the FURL being tried. | |
|
96 | # log.msg("FURL: %s" % self.furl) | |
|
97 | rr = yield self.tub.getReference(self.furl) | |
|
98 | except: | |
|
99 | if attempt==max_tries-1: | |
|
100 | # This will propagate the exception all the way to the top | |
|
101 | # where it can be handled. | |
|
102 | raise | |
|
103 | else: | |
|
104 | yield sleep_deferred(delay) | |
|
105 | yield self._try_to_connect( | |
|
106 | furl_or_file, 1.5*delay, max_tries, attempt+1 | |
|
107 | ) | |
|
108 | else: | |
|
109 | result = yield self._register(rr) | |
|
110 | returnValue(result) | |
|
70 | 111 | else: |
|
71 | d = self.tub.getReference(self.furl) | |
|
72 | d.addCallbacks(self._register, self._log_failure) | |
|
73 | return d | |
|
74 | ||
|
75 | def _log_failure(self, reason): | |
|
76 | log.err('EngineConnector: engine registration failed:') | |
|
77 | log.err(reason) | |
|
78 | return reason | |
|
112 | raise EngineConnectorError( | |
|
113 | 'Could not connect to controller, max_tries (%r) exceeded. ' | |
|
114 | 'This usually means that i) the controller was not started, ' | |
|
115 | 'or ii) a firewall was blocking the engine from connecting ' | |
|
116 | 'to the controller.' % max_tries | |
|
117 | ) | |
|
79 | 118 | |
|
80 | 119 | def _register(self, rr): |
|
81 | 120 | self.remote_ref = rr |
@@ -83,7 +122,7 b' class EngineConnector(object):' | |||
|
83 | 122 | desired_id = self.engine_service.id |
|
84 | 123 | d = self.remote_ref.callRemote('register_engine', self.engine_reference, |
|
85 | 124 | desired_id, os.getpid(), pickle.dumps(self.engine_service.properties,2)) |
|
86 |
return d.addCallback |
|
|
125 | return d.addCallback(self._reference_sent) | |
|
87 | 126 | |
|
88 | 127 | def _reference_sent(self, registration_dict): |
|
89 | 128 | self.engine_service.id = registration_dict['id'] |
@@ -15,6 +15,8 b' Foolscap related utilities.' | |||
|
15 | 15 | # Imports |
|
16 | 16 | #----------------------------------------------------------------------------- |
|
17 | 17 | |
|
18 | from __future__ import with_statement | |
|
19 | ||
|
18 | 20 | import os |
|
19 | 21 | import tempfile |
|
20 | 22 | |
@@ -85,10 +87,11 b' def find_furl(furl_or_file):' | |||
|
85 | 87 | if is_valid(furl_or_file): |
|
86 | 88 | return furl_or_file |
|
87 | 89 | if os.path.isfile(furl_or_file): |
|
88 |
|
|
|
90 | with open(furl_or_file, 'r') as f: | |
|
91 | furl = f.read().strip() | |
|
89 | 92 | if is_valid(furl): |
|
90 | 93 | return furl |
|
91 |
raise ValueError(" |
|
|
94 | raise ValueError("Not a FURL or a file containing a FURL: %s" % furl_or_file) | |
|
92 | 95 | |
|
93 | 96 | |
|
94 | 97 | def get_temp_furlfile(filename): |
@@ -213,6 +213,7 b' class IPControllerApp(ApplicationWithClusterDir):' | |||
|
213 | 213 | self.security_dir = config.Global.security_dir = sdir |
|
214 | 214 | ldir = self.cluster_dir_obj.log_dir |
|
215 | 215 | self.log_dir = config.Global.log_dir = ldir |
|
216 | self.log.info("Cluster directory set to: %s" % self.cluster_dir) | |
|
216 | 217 | self.log.info("Log directory set to: %s" % self.log_dir) |
|
217 | 218 | self.log.info("Security directory set to: %s" % self.security_dir) |
|
218 | 219 | |
@@ -266,3 +267,8 b' def launch_new_instance():' | |||
|
266 | 267 | """Create and run the IPython controller""" |
|
267 | 268 | app = IPControllerApp() |
|
268 | 269 | app.start() |
|
270 | ||
|
271 | ||
|
272 | if __name__ == '__main__': | |
|
273 | launch_new_instance() | |
|
274 |
@@ -133,29 +133,27 b' class IPEngineApp(ApplicationWithClusterDir):' | |||
|
133 | 133 | self.security_dir = config.Global.security_dir = sdir |
|
134 | 134 | ldir = self.cluster_dir_obj.log_dir |
|
135 | 135 | self.log_dir = config.Global.log_dir = ldir |
|
136 | self.log.info("Cluster directory set to: %s" % self.cluster_dir) | |
|
136 | 137 | self.log.info("Log directory set to: %s" % self.log_dir) |
|
137 | 138 | self.log.info("Security directory set to: %s" % self.security_dir) |
|
138 | 139 | |
|
139 | 140 | self.find_cont_furl_file() |
|
140 | 141 | |
|
141 | 142 | def find_cont_furl_file(self): |
|
143 | """Set the furl file. | |
|
144 | ||
|
145 | Here we don't try to actually see if it exists for is valid as that | |
|
146 | is hadled by the connection logic. | |
|
147 | """ | |
|
142 | 148 | config = self.master_config |
|
143 | 149 | # Find the actual controller FURL file |
|
144 |
if |
|
|
145 | return | |
|
146 | else: | |
|
147 | # We should know what the app dir is | |
|
150 | if not config.Global.furl_file: | |
|
148 | 151 | try_this = os.path.join( |
|
149 | 152 | config.Global.cluster_dir, |
|
150 | 153 | config.Global.security_dir, |
|
151 | 154 | config.Global.furl_file_name |
|
152 | 155 | ) |
|
153 |
if |
|
|
154 | config.Global.furl_file = try_this | |
|
155 | return | |
|
156 | else: | |
|
157 | self.log.critical("Could not find a valid controller FURL file.") | |
|
158 | self.abort() | |
|
156 | config.Global.furl_file = try_this | |
|
159 | 157 | |
|
160 | 158 | def construct(self): |
|
161 | 159 | # I am a little hesitant to put these into InteractiveShell itself. |
@@ -194,11 +192,11 b' class IPEngineApp(ApplicationWithClusterDir):' | |||
|
194 | 192 | ) |
|
195 | 193 | |
|
196 | 194 | def handle_error(f): |
|
197 | # If this print statement is replaced by a log.err(f) I get | |
|
198 | # an unhandled error, which makes no sense. I shouldn't have | |
|
199 | # to use a print statement here. My only thought is that | |
|
200 | # at the beginning of the process the logging is still starting up | |
|
201 | print "Error connecting to controller:", f.getErrorMessage() | |
|
195 | log.msg('Error connecting to controller. This usually means that ' | |
|
196 | 'i) the controller was not started, ii) a firewall was blocking ' | |
|
197 | 'the engine from connecting to the controller or iii) the engine ' | |
|
198 | ' was not pointed at the right FURL file:') | |
|
199 | log.msg(f.getErrorMessage()) | |
|
202 | 200 | reactor.callLater(0.1, reactor.stop) |
|
203 | 201 | |
|
204 | 202 | d.addErrback(handle_error) |
@@ -243,3 +241,8 b' def launch_new_instance():' | |||
|
243 | 241 | """Create and run the IPython controller""" |
|
244 | 242 | app = IPEngineApp() |
|
245 | 243 | app.start() |
|
244 | ||
|
245 | ||
|
246 | if __name__ == '__main__': | |
|
247 | launch_new_instance() | |
|
248 |
@@ -1,22 +1,18 b'' | |||
|
1 | 1 | #!/usr/bin/env python |
|
2 | 2 | # encoding: utf-8 |
|
3 | 3 | |
|
4 | """ipcluster script""" | |
|
5 | ||
|
6 | __docformat__ = "restructuredtext en" | |
|
7 | ||
|
8 | #------------------------------------------------------------------------------- | |
|
9 | # Copyright (C) 2008 The IPython Development Team | |
|
4 | #----------------------------------------------------------------------------- | |
|
5 | # Copyright (C) 2008-2009 The IPython Development Team | |
|
10 | 6 | # |
|
11 | 7 | # Distributed under the terms of the BSD License. The full license is in |
|
12 | 8 | # the file COPYING, distributed as part of this software. |
|
13 |
#----------------------------------------------------------------------------- |
|
|
9 | #----------------------------------------------------------------------------- | |
|
14 | 10 | |
|
15 |
#----------------------------------------------------------------------------- |
|
|
11 | #----------------------------------------------------------------------------- | |
|
16 | 12 | # Imports |
|
17 |
#----------------------------------------------------------------------------- |
|
|
13 | #----------------------------------------------------------------------------- | |
|
14 | ||
|
18 | 15 | |
|
19 | if __name__ == '__main__': | |
|
20 | from IPython.kernel.scripts import ipcluster | |
|
21 | ipcluster.main() | |
|
16 | from IPython.kernel.ipclusterapp import launch_new_instance | |
|
22 | 17 | |
|
18 | launch_new_instance() |
@@ -4,7 +4,7 b'' | |||
|
4 | 4 | """Start an IPython cluster = (controller + engines).""" |
|
5 | 5 | |
|
6 | 6 | #----------------------------------------------------------------------------- |
|
7 | # Copyright (C) 2008 The IPython Development Team | |
|
7 | # Copyright (C) 2008-2009 The IPython Development Team | |
|
8 | 8 | # |
|
9 | 9 | # Distributed under the terms of the BSD License. The full license is in |
|
10 | 10 | # the file COPYING, distributed as part of this software. |
@@ -25,7 +25,7 b' from twisted.internet import reactor, defer' | |||
|
25 | 25 | from twisted.internet.protocol import ProcessProtocol |
|
26 | 26 | from twisted.internet.error import ProcessDone, ProcessTerminated |
|
27 | 27 | from twisted.internet.utils import getProcessOutput |
|
28 |
from twisted.python import |
|
|
28 | from twisted.python import log | |
|
29 | 29 | |
|
30 | 30 | from IPython.external import argparse |
|
31 | 31 | from IPython.external import Itpl |
@@ -49,10 +49,8 b' get_log_dir()' | |||
|
49 | 49 | get_security_dir() |
|
50 | 50 | |
|
51 | 51 | from IPython.kernel.config import config_manager as kernel_config_manager |
|
52 | from IPython.kernel.error import SecurityError, FileTimeoutError | |
|
53 | from IPython.kernel.fcutil import have_crypto | |
|
54 | 52 | from IPython.kernel.twistedutil import gatherBoth, wait_for_file |
|
55 | from IPython.kernel.util import printer | |
|
53 | ||
|
56 | 54 | |
|
57 | 55 | #----------------------------------------------------------------------------- |
|
58 | 56 | # General process handling code |
@@ -140,7 +138,7 b' class ProcessLauncher(object):' | |||
|
140 | 138 | ) |
|
141 | 139 | return self.start_deferred |
|
142 | 140 | else: |
|
143 |
s = ' |
|
|
141 | s = 'The process has already been started and has state: %r' % \ | |
|
144 | 142 | self.state |
|
145 | 143 | return defer.fail(ProcessStateError(s)) |
|
146 | 144 |
@@ -247,3 +247,10 b' def wait_for_file(filename, delay=0.1, max_tries=10):' | |||
|
247 | 247 | |
|
248 | 248 | _test_for_file(filename) |
|
249 | 249 | return d |
|
250 | ||
|
251 | ||
|
252 | def sleep_deferred(seconds): | |
|
253 | """Sleep without blocking the event loop.""" | |
|
254 | d = defer.Deferred() | |
|
255 | reactor.callLater(seconds, d.callback, seconds) | |
|
256 | return d |
@@ -1,119 +1,137 b'' | |||
|
1 | #!/usr/bin/env python | |
|
1 | 2 | # encoding: utf-8 |
|
2 | ||
|
3 |
|
|
|
3 | """ | |
|
4 | The IPython Core Notification Center. | |
|
4 | 5 | |
|
5 | 6 | See docs/source/development/notification_blueprint.txt for an overview of the |
|
6 | 7 | notification module. |
|
8 | ||
|
9 | Authors: | |
|
10 | ||
|
11 | * Barry Wark | |
|
12 | * Brian Granger | |
|
7 | 13 | """ |
|
8 | 14 | |
|
9 | __docformat__ = "restructuredtext en" | |
|
10 | ||
|
11 | 15 | #----------------------------------------------------------------------------- |
|
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 | # Copyright (C) 2008-2009 The IPython Development Team | |
|
17 | # | |
|
18 | # Distributed under the terms of the BSD License. The full license is in | |
|
19 | # the file COPYING, distributed as part of this software. | |
|
20 | #----------------------------------------------------------------------------- | |
|
21 | ||
|
22 | #----------------------------------------------------------------------------- | |
|
23 | # Code | |
|
16 | 24 | #----------------------------------------------------------------------------- |
|
17 | 25 | |
|
18 | # Tell nose to skip the testing of this module | |
|
19 | __test__ = {} | |
|
26 | ||
|
27 | class NotificationError(Exception): | |
|
28 | pass | |
|
29 | ||
|
20 | 30 | |
|
21 | 31 | class NotificationCenter(object): |
|
22 | """Synchronous notification center | |
|
32 | """Synchronous notification center. | |
|
23 | 33 | |
|
24 | 34 | Examples |
|
25 | 35 | -------- |
|
26 | >>> import IPython.kernel.core.notification as notification | |
|
27 | >>> def callback(theType, theSender, args={}): | |
|
28 | ... print theType,theSender,args | |
|
29 | ... | |
|
30 | >>> notification.sharedCenter.add_observer(callback, 'NOTIFICATION_TYPE', None) | |
|
31 | >>> notification.sharedCenter.post_notification('NOTIFICATION_TYPE', object()) # doctest:+ELLIPSIS | |
|
32 | NOTIFICATION_TYPE ... | |
|
33 | ||
|
36 | Here is a simple example of how to use this:: | |
|
37 | ||
|
38 | import IPython.kernel.core.notification as notification | |
|
39 | def callback(ntype, theSender, args={}): | |
|
40 | print ntype,theSender,args | |
|
41 | ||
|
42 | notification.sharedCenter.add_observer(callback, 'NOTIFICATION_TYPE', None) | |
|
43 | notification.sharedCenter.post_notification('NOTIFICATION_TYPE', object()) # doctest:+ELLIPSIS | |
|
44 | NOTIFICATION_TYPE ... | |
|
34 | 45 | """ |
|
35 | 46 | def __init__(self): |
|
36 | 47 | super(NotificationCenter, self).__init__() |
|
37 | 48 | self._init_observers() |
|
38 | ||
|
39 | ||
|
49 | ||
|
40 | 50 | def _init_observers(self): |
|
41 | 51 | """Initialize observer storage""" |
|
42 | ||
|
52 | ||
|
43 | 53 | self.registered_types = set() #set of types that are observed |
|
44 | 54 | self.registered_senders = set() #set of senders that are observed |
|
45 | 55 | self.observers = {} #map (type,sender) => callback (callable) |
|
46 | ||
|
47 | ||
|
48 | def post_notification(self, theType, sender, **kwargs): | |
|
49 | """Post notification (type,sender,**kwargs) to all registered | |
|
50 | observers. | |
|
51 | 56 | |
|
52 | Implementation notes: | |
|
57 | def post_notification(self, ntype, sender, *args, **kwargs): | |
|
58 | """Post notification to all registered observers. | |
|
59 | ||
|
60 | The registered callback will be called as:: | |
|
61 | ||
|
62 | callback(ntype, sender, *args, **kwargs) | |
|
63 | ||
|
64 | Parameters | |
|
65 | ---------- | |
|
66 | ntype : hashable | |
|
67 | The notification type. | |
|
68 | sender : hashable | |
|
69 | The object sending the notification. | |
|
70 | *args : tuple | |
|
71 | The positional arguments to be passed to the callback. | |
|
72 | **kwargs : dict | |
|
73 | The keyword argument to be passed to the callback. | |
|
53 | 74 | |
|
75 | Notes | |
|
76 | ----- | |
|
54 | 77 | * If no registered observers, performance is O(1). |
|
55 | 78 | * Notificaiton order is undefined. |
|
56 | 79 | * Notifications are posted synchronously. |
|
57 | 80 | """ |
|
58 | ||
|
59 |
if(t |
|
|
60 | raise Exception("NotificationCenter.post_notification requires \ | |
|
61 | type and sender.") | |
|
62 | ||
|
81 | ||
|
82 | if(ntype==None or sender==None): | |
|
83 | raise NotificationError( | |
|
84 | "Notification type and sender are required.") | |
|
85 | ||
|
63 | 86 | # If there are no registered observers for the type/sender pair |
|
64 |
if((t |
|
|
87 | if((ntype not in self.registered_types and | |
|
65 | 88 | None not in self.registered_types) or |
|
66 | 89 | (sender not in self.registered_senders and |
|
67 | 90 | None not in self.registered_senders)): |
|
68 | 91 | return |
|
69 | ||
|
70 |
for o in self._observers_for_notification(t |
|
|
71 |
o(t |
|
|
72 | ||
|
73 | ||
|
74 | def _observers_for_notification(self, theType, sender): | |
|
92 | ||
|
93 | for o in self._observers_for_notification(ntype, sender): | |
|
94 | o(ntype, sender, *args, **kwargs) | |
|
95 | ||
|
96 | def _observers_for_notification(self, ntype, sender): | |
|
75 | 97 | """Find all registered observers that should recieve notification""" |
|
76 | ||
|
98 | ||
|
77 | 99 | keys = ( |
|
78 |
|
|
|
79 |
|
|
|
80 |
|
|
|
81 |
|
|
|
82 |
|
|
|
83 | ||
|
84 | ||
|
100 | (ntype,sender), | |
|
101 | (ntype, None), | |
|
102 | (None, sender), | |
|
103 | (None,None) | |
|
104 | ) | |
|
105 | ||
|
85 | 106 | obs = set() |
|
86 | 107 | for k in keys: |
|
87 | 108 | obs.update(self.observers.get(k, set())) |
|
88 | ||
|
109 | ||
|
89 | 110 | return obs |
|
90 | ||
|
91 | ||
|
92 | def add_observer(self, callback, theType, sender): | |
|
111 | ||
|
112 | def add_observer(self, callback, ntype, sender): | |
|
93 | 113 | """Add an observer callback to this notification center. |
|
94 | 114 | |
|
95 | 115 | The given callback will be called upon posting of notifications of |
|
96 |
the given type/sender and will receive any additional |
|
|
116 | the given type/sender and will receive any additional arguments passed | |
|
97 | 117 | to post_notification. |
|
98 | 118 | |
|
99 | 119 | Parameters |
|
100 | 120 | ---------- |
|
101 |
|
|
|
102 | Callable. Must take at least two arguments:: | |
|
103 |
|
|
|
104 | ||
|
105 | theType : hashable | |
|
121 | callback : callable | |
|
122 | The callable that will be called by :meth:`post_notification` | |
|
123 | as ``callback(ntype, sender, *args, **kwargs) | |
|
124 | ntype : hashable | |
|
106 | 125 | The notification type. If None, all notifications from sender |
|
107 | 126 | will be posted. |
|
108 | ||
|
109 | 127 | sender : hashable |
|
110 |
The notification sender. If None, all notifications of t |
|
|
128 | The notification sender. If None, all notifications of ntype | |
|
111 | 129 | will be posted. |
|
112 | 130 | """ |
|
113 | 131 | assert(callback != None) |
|
114 |
self.registered_types.add(t |
|
|
132 | self.registered_types.add(ntype) | |
|
115 | 133 | self.registered_senders.add(sender) |
|
116 |
self.observers.setdefault((t |
|
|
134 | self.observers.setdefault((ntype,sender), set()).add(callback) | |
|
117 | 135 | |
|
118 | 136 | def remove_all_observers(self): |
|
119 | 137 | """Removes all observers from this notification center""" |
@@ -122,4 +140,4 b' class NotificationCenter(object):' | |||
|
122 | 140 | |
|
123 | 141 | |
|
124 | 142 | |
|
125 |
shared |
|
|
143 | shared_center = NotificationCenter() |
@@ -13,135 +13,129 b'' | |||
|
13 | 13 | # Imports |
|
14 | 14 | #----------------------------------------------------------------------------- |
|
15 | 15 | |
|
16 | # Tell nose to skip this module | |
|
17 | __test__ = {} | |
|
16 | import unittest | |
|
18 | 17 | |
|
19 | from twisted.trial import unittest | |
|
20 | import IPython.kernel.core.notification as notification | |
|
18 | from IPython.utils.notification import ( | |
|
19 | NotificationCenter, | |
|
20 | NotificationError, | |
|
21 | shared_center | |
|
22 | ) | |
|
21 | 23 | |
|
22 | 24 | #----------------------------------------------------------------------------- |
|
23 | 25 | # Support Classes |
|
24 | 26 | #----------------------------------------------------------------------------- |
|
25 | 27 | |
|
28 | ||
|
26 | 29 | class Observer(object): |
|
27 | """docstring for Observer""" | |
|
28 |
def __init__(self, expected |
|
|
29 |
center= |
|
|
30 | ||
|
31 | def __init__(self, expected_ntype, expected_sender, | |
|
32 | center=shared_center, *args, **kwargs): | |
|
30 | 33 | super(Observer, self).__init__() |
|
31 |
self.expected |
|
|
32 |
self.expected |
|
|
33 |
self.expected |
|
|
34 | self.expected_ntype = expected_ntype | |
|
35 | self.expected_sender = expected_sender | |
|
36 | self.expected_args = args | |
|
37 | self.expected_kwargs = kwargs | |
|
34 | 38 | self.recieved = False |
|
35 | 39 | center.add_observer(self.callback, |
|
36 |
self.expected |
|
|
37 |
self.expected |
|
|
40 | self.expected_ntype, | |
|
41 | self.expected_sender) | |
|
38 | 42 | |
|
39 |
def callback(self, t |
|
|
40 | """callback""" | |
|
41 | ||
|
42 |
assert( |
|
|
43 |
self.expected |
|
|
44 |
assert( |
|
|
45 | self.expectedSender == None) | |
|
46 | assert(args == self.expectedKwArgs) | |
|
43 | def callback(self, ntype, sender, *args, **kwargs): | |
|
44 | assert(ntype == self.expected_ntype or | |
|
45 | self.expected_ntype == None) | |
|
46 | assert(sender == self.expected_sender or | |
|
47 | self.expected_sender == None) | |
|
48 | assert(args == self.expected_args) | |
|
49 | assert(kwargs == self.expected_kwargs) | |
|
47 | 50 | self.recieved = True |
|
48 | 51 | |
|
49 | 52 | def verify(self): |
|
50 | """verify""" | |
|
51 | ||
|
52 | 53 | assert(self.recieved) |
|
53 | 54 | |
|
54 | 55 | def reset(self): |
|
55 | """reset""" | |
|
56 | ||
|
57 | 56 | self.recieved = False |
|
58 | 57 | |
|
59 | 58 | |
|
60 | 59 | class Notifier(object): |
|
61 | """docstring for Notifier""" | |
|
62 |
def __init__(self, t |
|
|
60 | ||
|
61 | def __init__(self, ntype, **kwargs): | |
|
63 | 62 | super(Notifier, self).__init__() |
|
64 |
self.t |
|
|
63 | self.ntype = ntype | |
|
65 | 64 | self.kwargs = kwargs |
|
66 | ||
|
67 |
def post(self, center= |
|
|
68 | """fire""" | |
|
69 | ||
|
70 | center.post_notification(self.theType, self, | |
|
65 | ||
|
66 | def post(self, center=shared_center): | |
|
67 | ||
|
68 | center.post_notification(self.ntype, self, | |
|
71 | 69 | **self.kwargs) |
|
72 | 70 | |
|
71 | ||
|
73 | 72 | #----------------------------------------------------------------------------- |
|
74 | 73 | # Tests |
|
75 | 74 | #----------------------------------------------------------------------------- |
|
76 | 75 | |
|
76 | ||
|
77 | 77 | class NotificationTests(unittest.TestCase): |
|
78 | """docstring for NotificationTests""" | |
|
79 | ||
|
78 | ||
|
80 | 79 | def tearDown(self): |
|
81 |
|
|
|
80 | shared_center.remove_all_observers() | |
|
82 | 81 | |
|
83 | 82 | def test_notification_delivered(self): |
|
84 | 83 | """Test that notifications are delivered""" |
|
85 | expectedType = 'EXPECTED_TYPE' | |
|
86 | sender = Notifier(expectedType) | |
|
87 |
|
|
|
88 | ||
|
84 | ||
|
85 | expected_ntype = 'EXPECTED_TYPE' | |
|
86 | sender = Notifier(expected_ntype) | |
|
87 | observer = Observer(expected_ntype, sender) | |
|
88 | ||
|
89 | 89 | sender.post() |
|
90 | ||
|
91 | 90 | observer.verify() |
|
92 | ||
|
91 | ||
|
93 | 92 | def test_type_specificity(self): |
|
94 | 93 | """Test that observers are registered by type""" |
|
95 | ||
|
96 |
expected |
|
|
97 |
unexpected |
|
|
98 |
sender = Notifier(expected |
|
|
99 |
unexpected |
|
|
100 |
observer = Observer(expected |
|
|
101 | ||
|
94 | ||
|
95 | expected_ntype = 1 | |
|
96 | unexpected_ntype = "UNEXPECTED_TYPE" | |
|
97 | sender = Notifier(expected_ntype) | |
|
98 | unexpected_sender = Notifier(unexpected_ntype) | |
|
99 | observer = Observer(expected_ntype, sender) | |
|
100 | ||
|
102 | 101 | sender.post() |
|
103 |
unexpected |
|
|
104 | ||
|
102 | unexpected_sender.post() | |
|
105 | 103 | observer.verify() |
|
106 | ||
|
104 | ||
|
107 | 105 | def test_sender_specificity(self): |
|
108 | 106 | """Test that observers are registered by sender""" |
|
109 | 107 | |
|
110 |
expected |
|
|
111 |
sender1 = Notifier(expected |
|
|
112 |
sender2 = Notifier(expected |
|
|
113 |
observer = Observer(expected |
|
|
114 | ||
|
108 | expected_ntype = "EXPECTED_TYPE" | |
|
109 | sender1 = Notifier(expected_ntype) | |
|
110 | sender2 = Notifier(expected_ntype) | |
|
111 | observer = Observer(expected_ntype, sender1) | |
|
112 | ||
|
115 | 113 | sender1.post() |
|
116 | 114 | sender2.post() |
|
117 | ||
|
115 | ||
|
118 | 116 | observer.verify() |
|
119 | ||
|
117 | ||
|
120 | 118 | def test_remove_all_observers(self): |
|
121 | 119 | """White-box test for remove_all_observers""" |
|
122 | ||
|
120 | ||
|
123 | 121 | for i in xrange(10): |
|
124 |
Observer('TYPE', None, center= |
|
|
125 | ||
|
126 |
self.assert_(len( |
|
|
122 | Observer('TYPE', None, center=shared_center) | |
|
123 | ||
|
124 | self.assert_(len(shared_center.observers[('TYPE',None)]) >= 10, | |
|
127 | 125 | "observers registered") |
|
128 | ||
|
129 |
|
|
|
130 | ||
|
131 | self.assert_(len(notification.sharedCenter.observers) == 0, "observers removed") | |
|
126 | ||
|
127 | shared_center.remove_all_observers() | |
|
128 | self.assert_(len(shared_center.observers) == 0, "observers removed") | |
|
132 | 129 | |
|
133 | 130 | def test_any_sender(self): |
|
134 | """test_any_sender""" | |
|
135 | ||
|
136 | expectedType = "EXPECTED_TYPE" | |
|
137 |
|
|
|
138 | sender2 = Notifier(expectedType) | |
|
139 | observer = Observer(expectedType, None) | |
|
140 | ||
|
141 | ||
|
131 | expected_ntype = "EXPECTED_TYPE" | |
|
132 | sender1 = Notifier(expected_ntype) | |
|
133 | sender2 = Notifier(expected_ntype) | |
|
134 | observer = Observer(expected_ntype, None) | |
|
135 | ||
|
142 | 136 | sender1.post() |
|
143 | 137 | observer.verify() |
|
144 | ||
|
138 | ||
|
145 | 139 | observer.reset() |
|
146 | 140 | sender2.post() |
|
147 | 141 | observer.verify() |
@@ -152,10 +146,9 b' class NotificationTests(unittest.TestCase):' | |||
|
152 | 146 | |
|
153 | 147 | for i in xrange(10): |
|
154 | 148 | Observer("UNRELATED_TYPE", None) |
|
155 | ||
|
149 | ||
|
156 | 150 | o = Observer('EXPECTED_TYPE', None) |
|
157 | ||
|
158 | notification.sharedCenter.post_notification('EXPECTED_TYPE', self) | |
|
159 | ||
|
151 | shared_center.post_notification('EXPECTED_TYPE', self) | |
|
160 | 152 | o.verify() |
|
161 | 153 | |
|
154 |
@@ -171,7 +171,7 b" if 'setuptools' in sys.modules:" | |||
|
171 | 171 | 'pycolor = IPython.utils.PyColorize:main', |
|
172 | 172 | 'ipcontroller = IPython.kernel.ipcontrollerapp:launch_new_instance', |
|
173 | 173 | 'ipengine = IPython.kernel.ipengineapp:launch_new_instance', |
|
174 |
'ipcluster = IPython.kernel. |
|
|
174 | 'ipcluster = IPython.kernel.ipclusterapp:launch_new_instance', | |
|
175 | 175 | 'ipythonx = IPython.frontend.wx.ipythonx:main', |
|
176 | 176 | 'iptest = IPython.testing.iptest:main', |
|
177 | 177 | 'irunner = IPython.lib.irunner:main' |
General Comments 0
You need to be logged in to leave comments.
Login now