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