##// END OF EJS Templates
still allow @interactive on Classes
MinRK -
Show More
@@ -1,379 +1,380 b''
1 """some generic utilities for dealing with classes, urls, and serialization
1 """some generic utilities for dealing with classes, urls, and serialization
2
2
3 Authors:
3 Authors:
4
4
5 * Min RK
5 * Min RK
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2010-2011 The IPython Development Team
8 # Copyright (C) 2010-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import logging
19 import logging
20 import os
20 import os
21 import re
21 import re
22 import stat
22 import stat
23 import socket
23 import socket
24 import sys
24 import sys
25 from signal import signal, SIGINT, SIGABRT, SIGTERM
25 from signal import signal, SIGINT, SIGABRT, SIGTERM
26 try:
26 try:
27 from signal import SIGKILL
27 from signal import SIGKILL
28 except ImportError:
28 except ImportError:
29 SIGKILL=None
29 SIGKILL=None
30 from types import FunctionType
30 from types import FunctionType
31
31
32 try:
32 try:
33 import cPickle
33 import cPickle
34 pickle = cPickle
34 pickle = cPickle
35 except:
35 except:
36 cPickle = None
36 cPickle = None
37 import pickle
37 import pickle
38
38
39 # System library imports
39 # System library imports
40 import zmq
40 import zmq
41 from zmq.log import handlers
41 from zmq.log import handlers
42
42
43 from IPython.external.decorator import decorator
43 from IPython.external.decorator import decorator
44
44
45 # IPython imports
45 # IPython imports
46 from IPython.config.application import Application
46 from IPython.config.application import Application
47 from IPython.utils.localinterfaces import localhost, is_public_ip, public_ips
47 from IPython.utils.localinterfaces import localhost, is_public_ip, public_ips
48 from IPython.utils.py3compat import string_types, iteritems, itervalues
48 from IPython.utils.py3compat import string_types, iteritems, itervalues
49 from IPython.kernel.zmq.log import EnginePUBHandler
49 from IPython.kernel.zmq.log import EnginePUBHandler
50 from IPython.kernel.zmq.serialize import (
50 from IPython.kernel.zmq.serialize import (
51 unserialize_object, serialize_object, pack_apply_message, unpack_apply_message
51 unserialize_object, serialize_object, pack_apply_message, unpack_apply_message
52 )
52 )
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # Classes
55 # Classes
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57
57
58 class Namespace(dict):
58 class Namespace(dict):
59 """Subclass of dict for attribute access to keys."""
59 """Subclass of dict for attribute access to keys."""
60
60
61 def __getattr__(self, key):
61 def __getattr__(self, key):
62 """getattr aliased to getitem"""
62 """getattr aliased to getitem"""
63 if key in self:
63 if key in self:
64 return self[key]
64 return self[key]
65 else:
65 else:
66 raise NameError(key)
66 raise NameError(key)
67
67
68 def __setattr__(self, key, value):
68 def __setattr__(self, key, value):
69 """setattr aliased to setitem, with strict"""
69 """setattr aliased to setitem, with strict"""
70 if hasattr(dict, key):
70 if hasattr(dict, key):
71 raise KeyError("Cannot override dict keys %r"%key)
71 raise KeyError("Cannot override dict keys %r"%key)
72 self[key] = value
72 self[key] = value
73
73
74
74
75 class ReverseDict(dict):
75 class ReverseDict(dict):
76 """simple double-keyed subset of dict methods."""
76 """simple double-keyed subset of dict methods."""
77
77
78 def __init__(self, *args, **kwargs):
78 def __init__(self, *args, **kwargs):
79 dict.__init__(self, *args, **kwargs)
79 dict.__init__(self, *args, **kwargs)
80 self._reverse = dict()
80 self._reverse = dict()
81 for key, value in iteritems(self):
81 for key, value in iteritems(self):
82 self._reverse[value] = key
82 self._reverse[value] = key
83
83
84 def __getitem__(self, key):
84 def __getitem__(self, key):
85 try:
85 try:
86 return dict.__getitem__(self, key)
86 return dict.__getitem__(self, key)
87 except KeyError:
87 except KeyError:
88 return self._reverse[key]
88 return self._reverse[key]
89
89
90 def __setitem__(self, key, value):
90 def __setitem__(self, key, value):
91 if key in self._reverse:
91 if key in self._reverse:
92 raise KeyError("Can't have key %r on both sides!"%key)
92 raise KeyError("Can't have key %r on both sides!"%key)
93 dict.__setitem__(self, key, value)
93 dict.__setitem__(self, key, value)
94 self._reverse[value] = key
94 self._reverse[value] = key
95
95
96 def pop(self, key):
96 def pop(self, key):
97 value = dict.pop(self, key)
97 value = dict.pop(self, key)
98 self._reverse.pop(value)
98 self._reverse.pop(value)
99 return value
99 return value
100
100
101 def get(self, key, default=None):
101 def get(self, key, default=None):
102 try:
102 try:
103 return self[key]
103 return self[key]
104 except KeyError:
104 except KeyError:
105 return default
105 return default
106
106
107 #-----------------------------------------------------------------------------
107 #-----------------------------------------------------------------------------
108 # Functions
108 # Functions
109 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
110
110
111 @decorator
111 @decorator
112 def log_errors(f, self, *args, **kwargs):
112 def log_errors(f, self, *args, **kwargs):
113 """decorator to log unhandled exceptions raised in a method.
113 """decorator to log unhandled exceptions raised in a method.
114
114
115 For use wrapping on_recv callbacks, so that exceptions
115 For use wrapping on_recv callbacks, so that exceptions
116 do not cause the stream to be closed.
116 do not cause the stream to be closed.
117 """
117 """
118 try:
118 try:
119 return f(self, *args, **kwargs)
119 return f(self, *args, **kwargs)
120 except Exception:
120 except Exception:
121 self.log.error("Uncaught exception in %r" % f, exc_info=True)
121 self.log.error("Uncaught exception in %r" % f, exc_info=True)
122
122
123
123
124 def is_url(url):
124 def is_url(url):
125 """boolean check for whether a string is a zmq url"""
125 """boolean check for whether a string is a zmq url"""
126 if '://' not in url:
126 if '://' not in url:
127 return False
127 return False
128 proto, addr = url.split('://', 1)
128 proto, addr = url.split('://', 1)
129 if proto.lower() not in ['tcp','pgm','epgm','ipc','inproc']:
129 if proto.lower() not in ['tcp','pgm','epgm','ipc','inproc']:
130 return False
130 return False
131 return True
131 return True
132
132
133 def validate_url(url):
133 def validate_url(url):
134 """validate a url for zeromq"""
134 """validate a url for zeromq"""
135 if not isinstance(url, string_types):
135 if not isinstance(url, string_types):
136 raise TypeError("url must be a string, not %r"%type(url))
136 raise TypeError("url must be a string, not %r"%type(url))
137 url = url.lower()
137 url = url.lower()
138
138
139 proto_addr = url.split('://')
139 proto_addr = url.split('://')
140 assert len(proto_addr) == 2, 'Invalid url: %r'%url
140 assert len(proto_addr) == 2, 'Invalid url: %r'%url
141 proto, addr = proto_addr
141 proto, addr = proto_addr
142 assert proto in ['tcp','pgm','epgm','ipc','inproc'], "Invalid protocol: %r"%proto
142 assert proto in ['tcp','pgm','epgm','ipc','inproc'], "Invalid protocol: %r"%proto
143
143
144 # domain pattern adapted from http://www.regexlib.com/REDetails.aspx?regexp_id=391
144 # domain pattern adapted from http://www.regexlib.com/REDetails.aspx?regexp_id=391
145 # author: Remi Sabourin
145 # author: Remi Sabourin
146 pat = re.compile(r'^([\w\d]([\w\d\-]{0,61}[\w\d])?\.)*[\w\d]([\w\d\-]{0,61}[\w\d])?$')
146 pat = re.compile(r'^([\w\d]([\w\d\-]{0,61}[\w\d])?\.)*[\w\d]([\w\d\-]{0,61}[\w\d])?$')
147
147
148 if proto == 'tcp':
148 if proto == 'tcp':
149 lis = addr.split(':')
149 lis = addr.split(':')
150 assert len(lis) == 2, 'Invalid url: %r'%url
150 assert len(lis) == 2, 'Invalid url: %r'%url
151 addr,s_port = lis
151 addr,s_port = lis
152 try:
152 try:
153 port = int(s_port)
153 port = int(s_port)
154 except ValueError:
154 except ValueError:
155 raise AssertionError("Invalid port %r in url: %r"%(port, url))
155 raise AssertionError("Invalid port %r in url: %r"%(port, url))
156
156
157 assert addr == '*' or pat.match(addr) is not None, 'Invalid url: %r'%url
157 assert addr == '*' or pat.match(addr) is not None, 'Invalid url: %r'%url
158
158
159 else:
159 else:
160 # only validate tcp urls currently
160 # only validate tcp urls currently
161 pass
161 pass
162
162
163 return True
163 return True
164
164
165
165
166 def validate_url_container(container):
166 def validate_url_container(container):
167 """validate a potentially nested collection of urls."""
167 """validate a potentially nested collection of urls."""
168 if isinstance(container, string_types):
168 if isinstance(container, string_types):
169 url = container
169 url = container
170 return validate_url(url)
170 return validate_url(url)
171 elif isinstance(container, dict):
171 elif isinstance(container, dict):
172 container = itervalues(container)
172 container = itervalues(container)
173
173
174 for element in container:
174 for element in container:
175 validate_url_container(element)
175 validate_url_container(element)
176
176
177
177
178 def split_url(url):
178 def split_url(url):
179 """split a zmq url (tcp://ip:port) into ('tcp','ip','port')."""
179 """split a zmq url (tcp://ip:port) into ('tcp','ip','port')."""
180 proto_addr = url.split('://')
180 proto_addr = url.split('://')
181 assert len(proto_addr) == 2, 'Invalid url: %r'%url
181 assert len(proto_addr) == 2, 'Invalid url: %r'%url
182 proto, addr = proto_addr
182 proto, addr = proto_addr
183 lis = addr.split(':')
183 lis = addr.split(':')
184 assert len(lis) == 2, 'Invalid url: %r'%url
184 assert len(lis) == 2, 'Invalid url: %r'%url
185 addr,s_port = lis
185 addr,s_port = lis
186 return proto,addr,s_port
186 return proto,addr,s_port
187
187
188 def disambiguate_ip_address(ip, location=None):
188 def disambiguate_ip_address(ip, location=None):
189 """turn multi-ip interfaces '0.0.0.0' and '*' into connectable
189 """turn multi-ip interfaces '0.0.0.0' and '*' into connectable
190 ones, based on the location (default interpretation of location is localhost)."""
190 ones, based on the location (default interpretation of location is localhost)."""
191 if ip in ('0.0.0.0', '*'):
191 if ip in ('0.0.0.0', '*'):
192 if location is None or is_public_ip(location) or not public_ips():
192 if location is None or is_public_ip(location) or not public_ips():
193 # If location is unspecified or cannot be determined, assume local
193 # If location is unspecified or cannot be determined, assume local
194 ip = localhost()
194 ip = localhost()
195 elif location:
195 elif location:
196 return location
196 return location
197 return ip
197 return ip
198
198
199 def disambiguate_url(url, location=None):
199 def disambiguate_url(url, location=None):
200 """turn multi-ip interfaces '0.0.0.0' and '*' into connectable
200 """turn multi-ip interfaces '0.0.0.0' and '*' into connectable
201 ones, based on the location (default interpretation is localhost).
201 ones, based on the location (default interpretation is localhost).
202
202
203 This is for zeromq urls, such as ``tcp://*:10101``.
203 This is for zeromq urls, such as ``tcp://*:10101``.
204 """
204 """
205 try:
205 try:
206 proto,ip,port = split_url(url)
206 proto,ip,port = split_url(url)
207 except AssertionError:
207 except AssertionError:
208 # probably not tcp url; could be ipc, etc.
208 # probably not tcp url; could be ipc, etc.
209 return url
209 return url
210
210
211 ip = disambiguate_ip_address(ip,location)
211 ip = disambiguate_ip_address(ip,location)
212
212
213 return "%s://%s:%s"%(proto,ip,port)
213 return "%s://%s:%s"%(proto,ip,port)
214
214
215
215
216 #--------------------------------------------------------------------------
216 #--------------------------------------------------------------------------
217 # helpers for implementing old MEC API via view.apply
217 # helpers for implementing old MEC API via view.apply
218 #--------------------------------------------------------------------------
218 #--------------------------------------------------------------------------
219
219
220 def interactive(f):
220 def interactive(f):
221 """decorator for making functions appear as interactively defined.
221 """decorator for making functions appear as interactively defined.
222 This results in the function being linked to the user_ns as globals()
222 This results in the function being linked to the user_ns as globals()
223 instead of the module globals().
223 instead of the module globals().
224 """
224 """
225 mainmod = __import__('__main__')
226
225
227 # build new FunctionType, so it can have the right globals
226 # build new FunctionType, so it can have the right globals
228 # interactive functions never have closures, that's kind of the point
227 # interactive functions never have closures, that's kind of the point
229 f2 = FunctionType(f.__code__, mainmod.__dict__,
228 if isinstance(f, FunctionType):
230 f.__name__, f.__defaults__,
229 mainmod = __import__('__main__')
231 )
230 f = FunctionType(f.__code__, mainmod.__dict__,
231 f.__name__, f.__defaults__,
232 )
232 # associate with __main__ for uncanning
233 # associate with __main__ for uncanning
233 f2.__module__ = '__main__'
234 f.__module__ = '__main__'
234 return f2
235 return f
235
236
236 @interactive
237 @interactive
237 def _push(**ns):
238 def _push(**ns):
238 """helper method for implementing `client.push` via `client.apply`"""
239 """helper method for implementing `client.push` via `client.apply`"""
239 user_ns = globals()
240 user_ns = globals()
240 tmp = '_IP_PUSH_TMP_'
241 tmp = '_IP_PUSH_TMP_'
241 while tmp in user_ns:
242 while tmp in user_ns:
242 tmp = tmp + '_'
243 tmp = tmp + '_'
243 try:
244 try:
244 for name, value in ns.items():
245 for name, value in ns.items():
245 user_ns[tmp] = value
246 user_ns[tmp] = value
246 exec("%s = %s" % (name, tmp), user_ns)
247 exec("%s = %s" % (name, tmp), user_ns)
247 finally:
248 finally:
248 user_ns.pop(tmp, None)
249 user_ns.pop(tmp, None)
249
250
250 @interactive
251 @interactive
251 def _pull(keys):
252 def _pull(keys):
252 """helper method for implementing `client.pull` via `client.apply`"""
253 """helper method for implementing `client.pull` via `client.apply`"""
253 if isinstance(keys, (list,tuple, set)):
254 if isinstance(keys, (list,tuple, set)):
254 return [eval(key, globals()) for key in keys]
255 return [eval(key, globals()) for key in keys]
255 else:
256 else:
256 return eval(keys, globals())
257 return eval(keys, globals())
257
258
258 @interactive
259 @interactive
259 def _execute(code):
260 def _execute(code):
260 """helper method for implementing `client.execute` via `client.apply`"""
261 """helper method for implementing `client.execute` via `client.apply`"""
261 exec(code, globals())
262 exec(code, globals())
262
263
263 #--------------------------------------------------------------------------
264 #--------------------------------------------------------------------------
264 # extra process management utilities
265 # extra process management utilities
265 #--------------------------------------------------------------------------
266 #--------------------------------------------------------------------------
266
267
267 _random_ports = set()
268 _random_ports = set()
268
269
269 def select_random_ports(n):
270 def select_random_ports(n):
270 """Selects and return n random ports that are available."""
271 """Selects and return n random ports that are available."""
271 ports = []
272 ports = []
272 for i in range(n):
273 for i in range(n):
273 sock = socket.socket()
274 sock = socket.socket()
274 sock.bind(('', 0))
275 sock.bind(('', 0))
275 while sock.getsockname()[1] in _random_ports:
276 while sock.getsockname()[1] in _random_ports:
276 sock.close()
277 sock.close()
277 sock = socket.socket()
278 sock = socket.socket()
278 sock.bind(('', 0))
279 sock.bind(('', 0))
279 ports.append(sock)
280 ports.append(sock)
280 for i, sock in enumerate(ports):
281 for i, sock in enumerate(ports):
281 port = sock.getsockname()[1]
282 port = sock.getsockname()[1]
282 sock.close()
283 sock.close()
283 ports[i] = port
284 ports[i] = port
284 _random_ports.add(port)
285 _random_ports.add(port)
285 return ports
286 return ports
286
287
287 def signal_children(children):
288 def signal_children(children):
288 """Relay interupt/term signals to children, for more solid process cleanup."""
289 """Relay interupt/term signals to children, for more solid process cleanup."""
289 def terminate_children(sig, frame):
290 def terminate_children(sig, frame):
290 log = Application.instance().log
291 log = Application.instance().log
291 log.critical("Got signal %i, terminating children..."%sig)
292 log.critical("Got signal %i, terminating children..."%sig)
292 for child in children:
293 for child in children:
293 child.terminate()
294 child.terminate()
294
295
295 sys.exit(sig != SIGINT)
296 sys.exit(sig != SIGINT)
296 # sys.exit(sig)
297 # sys.exit(sig)
297 for sig in (SIGINT, SIGABRT, SIGTERM):
298 for sig in (SIGINT, SIGABRT, SIGTERM):
298 signal(sig, terminate_children)
299 signal(sig, terminate_children)
299
300
300 def generate_exec_key(keyfile):
301 def generate_exec_key(keyfile):
301 import uuid
302 import uuid
302 newkey = str(uuid.uuid4())
303 newkey = str(uuid.uuid4())
303 with open(keyfile, 'w') as f:
304 with open(keyfile, 'w') as f:
304 # f.write('ipython-key ')
305 # f.write('ipython-key ')
305 f.write(newkey+'\n')
306 f.write(newkey+'\n')
306 # set user-only RW permissions (0600)
307 # set user-only RW permissions (0600)
307 # this will have no effect on Windows
308 # this will have no effect on Windows
308 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
309 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
309
310
310
311
311 def integer_loglevel(loglevel):
312 def integer_loglevel(loglevel):
312 try:
313 try:
313 loglevel = int(loglevel)
314 loglevel = int(loglevel)
314 except ValueError:
315 except ValueError:
315 if isinstance(loglevel, str):
316 if isinstance(loglevel, str):
316 loglevel = getattr(logging, loglevel)
317 loglevel = getattr(logging, loglevel)
317 return loglevel
318 return loglevel
318
319
319 def connect_logger(logname, context, iface, root="ip", loglevel=logging.DEBUG):
320 def connect_logger(logname, context, iface, root="ip", loglevel=logging.DEBUG):
320 logger = logging.getLogger(logname)
321 logger = logging.getLogger(logname)
321 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
322 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
322 # don't add a second PUBHandler
323 # don't add a second PUBHandler
323 return
324 return
324 loglevel = integer_loglevel(loglevel)
325 loglevel = integer_loglevel(loglevel)
325 lsock = context.socket(zmq.PUB)
326 lsock = context.socket(zmq.PUB)
326 lsock.connect(iface)
327 lsock.connect(iface)
327 handler = handlers.PUBHandler(lsock)
328 handler = handlers.PUBHandler(lsock)
328 handler.setLevel(loglevel)
329 handler.setLevel(loglevel)
329 handler.root_topic = root
330 handler.root_topic = root
330 logger.addHandler(handler)
331 logger.addHandler(handler)
331 logger.setLevel(loglevel)
332 logger.setLevel(loglevel)
332
333
333 def connect_engine_logger(context, iface, engine, loglevel=logging.DEBUG):
334 def connect_engine_logger(context, iface, engine, loglevel=logging.DEBUG):
334 logger = logging.getLogger()
335 logger = logging.getLogger()
335 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
336 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
336 # don't add a second PUBHandler
337 # don't add a second PUBHandler
337 return
338 return
338 loglevel = integer_loglevel(loglevel)
339 loglevel = integer_loglevel(loglevel)
339 lsock = context.socket(zmq.PUB)
340 lsock = context.socket(zmq.PUB)
340 lsock.connect(iface)
341 lsock.connect(iface)
341 handler = EnginePUBHandler(engine, lsock)
342 handler = EnginePUBHandler(engine, lsock)
342 handler.setLevel(loglevel)
343 handler.setLevel(loglevel)
343 logger.addHandler(handler)
344 logger.addHandler(handler)
344 logger.setLevel(loglevel)
345 logger.setLevel(loglevel)
345 return logger
346 return logger
346
347
347 def local_logger(logname, loglevel=logging.DEBUG):
348 def local_logger(logname, loglevel=logging.DEBUG):
348 loglevel = integer_loglevel(loglevel)
349 loglevel = integer_loglevel(loglevel)
349 logger = logging.getLogger(logname)
350 logger = logging.getLogger(logname)
350 if any([isinstance(h, logging.StreamHandler) for h in logger.handlers]):
351 if any([isinstance(h, logging.StreamHandler) for h in logger.handlers]):
351 # don't add a second StreamHandler
352 # don't add a second StreamHandler
352 return
353 return
353 handler = logging.StreamHandler()
354 handler = logging.StreamHandler()
354 handler.setLevel(loglevel)
355 handler.setLevel(loglevel)
355 formatter = logging.Formatter("%(asctime)s.%(msecs).03d [%(name)s] %(message)s",
356 formatter = logging.Formatter("%(asctime)s.%(msecs).03d [%(name)s] %(message)s",
356 datefmt="%Y-%m-%d %H:%M:%S")
357 datefmt="%Y-%m-%d %H:%M:%S")
357 handler.setFormatter(formatter)
358 handler.setFormatter(formatter)
358
359
359 logger.addHandler(handler)
360 logger.addHandler(handler)
360 logger.setLevel(loglevel)
361 logger.setLevel(loglevel)
361 return logger
362 return logger
362
363
363 def set_hwm(sock, hwm=0):
364 def set_hwm(sock, hwm=0):
364 """set zmq High Water Mark on a socket
365 """set zmq High Water Mark on a socket
365
366
366 in a way that always works for various pyzmq / libzmq versions.
367 in a way that always works for various pyzmq / libzmq versions.
367 """
368 """
368 import zmq
369 import zmq
369
370
370 for key in ('HWM', 'SNDHWM', 'RCVHWM'):
371 for key in ('HWM', 'SNDHWM', 'RCVHWM'):
371 opt = getattr(zmq, key, None)
372 opt = getattr(zmq, key, None)
372 if opt is None:
373 if opt is None:
373 continue
374 continue
374 try:
375 try:
375 sock.setsockopt(opt, hwm)
376 sock.setsockopt(opt, hwm)
376 except zmq.ZMQError:
377 except zmq.ZMQError:
377 pass
378 pass
378
379
379 No newline at end of file
380
General Comments 0
You need to be logged in to leave comments. Login now