##// END OF EJS Templates
make js / Python widgets symmetrical...
MinRK -
Show More
@@ -1,121 +1,148 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2013 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Widget and WidgetManager bases
10 10 //============================================================================
11 11 /**
12 12 * Base Widget classes
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule widget
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19 "use strict";
20 20
21 21 //-----------------------------------------------------------------------
22 22 // WidgetManager class
23 23 //-----------------------------------------------------------------------
24 24
25 25 var WidgetManager = function (kernel) {
26 26 this.widgets = {};
27 27 this.widget_types = {widget : Widget};
28 28 if (kernel !== undefined) {
29 29 this.init_kernel(kernel);
30 30 }
31 31 };
32 32
33 33 WidgetManager.prototype.init_kernel = function (kernel) {
34 // connect the kernel, and register message handlers
34 35 this.kernel = kernel;
35 36 var msg_types = ['widget_create', 'widget_destroy', 'widget_update'];
36 37 for (var i = 0; i < msg_types.length; i++) {
37 38 var msg_type = msg_types[i];
38 39 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
39 40 }
40 41 };
41 42
42 43 WidgetManager.prototype.register_widget_type = function (widget_type, constructor) {
43 44 // Register a constructor for a given widget type name
44 45 this.widget_types[widget_type] = constructor;
45 46 };
46 47
48 WidgetManager.prototype.register_widget = function (widget) {
49 // Register a widget in the mapping
50 this.widgets[widget.widget_id] = widget;
51 widget.kernel = this.kernel;
52 return widget.widget_id;
53 };
54
55 WidgetManager.prototype.unregister_widget = function (widget_id) {
56 // Remove a widget from the mapping
57 delete this.widgets[widget_id];
58 };
59
60 // widget message handlers
61
47 62 WidgetManager.prototype.widget_create = function (msg) {
48 63 var content = msg.content;
49 64 var constructor = this.widget_types[content.widget_type];
50 65 if (constructor === undefined) {
51 66 console.log("No such widget type registered: ", content.widget_type);
52 67 console.log("Available widget types are: ", this.widget_types);
53 68 return;
54 69 }
55 var widget = new constructor(this.kernel, content);
70 var widget = new constructor(content.widget_id);
71 this.register_widget(widget);
72 widget.handle_create(content.data);
73
56 74 this.widgets[content.widget_id] = widget;
57 75 };
58 76
59 77 WidgetManager.prototype.widget_destroy = function (msg) {
60 78 var content = msg.content;
61 79 var widget = this.widgets[content.widget_id];
62 80 if (widget === undefined) {
63 81 return;
64 82 }
65 83 delete this.widgets[content.widget_id];
66 84 widget.handle_destroy(content.data);
67 85 };
68 86
69 87 WidgetManager.prototype.widget_update = function (msg) {
70 88 var content = msg.content;
71 89 var widget = this.widgets[content.widget_id];
72 90 if (widget === undefined) {
73 91 return;
74 92 }
75 93 widget.handle_update(content.data);
76 94 };
77 95
78 96 //-----------------------------------------------------------------------
79 97 // Widget base class
80 98 //-----------------------------------------------------------------------
81 99
82 var Widget = function (kernel, content) {
83 this.kernel = kernel;
84 if (!content) return;
85 this.widget_id = content.widget_id;
86 this.handle_create(content.data);
87 };
88
89 Widget.prototype.handle_create = function (data) {
100 var Widget = function (widget_id) {
101 this.widget_id = widget_id;
102 this.widget_type = 'widget';
90 103 };
91 104
92 Widget.prototype.handle_update = function (data) {
93 };
94
95 Widget.prototype.handle_destroy = function (data) {
105 // methods for sending messages
106 Widget.prototype.create = function (data) {
107 var content = {
108 widget_id : this.widget_id,
109 widget_type : this.widget_type,
110 data : data || {},
111 };
112 this.kernel.send_shell_message("widget_create", content);
96 113 };
97 114
98 115 Widget.prototype.update = function (data) {
99 116 var content = {
100 117 widget_id : this.widget_id,
101 data : data,
118 data : data || {},
102 119 };
103 120 this.kernel.send_shell_message("widget_update", content);
104 121 };
105 122
106
107 123 Widget.prototype.destroy = function (data) {
108 124 var content = {
109 125 widget_id : this.widget_id,
110 data : data,
126 data : data || {},
111 127 };
112 128 this.kernel.send_shell_message("widget_destroy", content);
113 129 };
114 130
131 // methods for handling incoming messages
132
133 Widget.prototype.handle_create = function (data) {
134 };
135
136 Widget.prototype.handle_update = function (data) {
137 };
138
139 Widget.prototype.handle_destroy = function (data) {
140 };
141
115 142 IPython.WidgetManager = WidgetManager;
116 143 IPython.Widget = Widget;
117 144
118 145 return IPython;
119 146
120 147 }(IPython));
121 148
@@ -1,115 +1,134 b''
1 1 """Base class to manage widgets"""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2013 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 from weakref import ref
15
16 14 from IPython.config import LoggingConfigurable
17 15 from IPython.core.prompts import LazyEvaluate
18 16 from IPython.core.getipython import get_ipython
17
18 from IPython.utils.importstring import import_item
19 19 from IPython.utils.traitlets import Instance, Unicode, Dict, Any
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Code
23 23 #-----------------------------------------------------------------------------
24 24
25 25 def lazy_keys(dikt):
26 26 """Return lazy-evaluated string representation of a dictionary's keys
27 27
28 28 Key list is only constructed if it will actually be used.
29 29 Used for debug-logging.
30 30 """
31 31 return LazyEvaluate(lambda d: list(d.keys()))
32 32
33
33 34 class WidgetManager(LoggingConfigurable):
34 35 """Manager for Widgets in the Kernel"""
35 36
36 37 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
37 38 def _shell_default(self):
38 39 return get_ipython()
39 40 iopub_socket = Any()
40 41 def _iopub_socket_default(self):
41 42 return self.shell.parent.iopub_socket
42 43 session = Instance('IPython.kernel.zmq.session.Session')
43 44 def _session_default(self):
44 45 if self.shell is None:
45 46 return
46 47 return self.shell.parent.session
47 48
48 49 widgets = Dict()
50 widget_types = Dict()
49 51
50 52 # Public APIs
51 53
54 def register_widget_type(self, widget_type, constructor):
55 """Register a constructor for a given widget_type
56
57 constructor can be a Widget class or an importstring for a Widget class.
58 """
59 if isinstance(constructor, basestring):
60 constructor = import_item(constructor)
61
62 self.widget_types[widget_type] = constructor
63
52 64 def register_widget(self, widget):
53 65 """Register a new widget"""
54 self.widgets[widget.widget_id] = ref(widget)
66 widget_id = widget.widget_id
55 67 widget.shell = self.shell
56 68 widget.iopub_socket = self.iopub_socket
57 widget.create()
58 return widget.widget_id
69 self.widgets[widget_id] = widget
70 return widget_id
59 71
60 72 def unregister_widget(self, widget_id):
61 73 """Unregister a widget, and destroy its counterpart"""
62 74 # unlike get_widget, this should raise a KeyError
63 widget_ref = self.widgets.pop(widget_id)
64 widget = widget_ref()
65 if widget is None:
66 # already destroyed, nothing to do
67 return
75 widget = self.widgets.pop(widget_id)
68 76 widget.destroy()
69 77
70 78 def get_widget(self, widget_id):
71 79 """Get a widget with a particular id
72 80
73 81 Returns the widget if found, otherwise None.
74 82
75 83 This will not raise an error,
76 84 it will log messages if the widget cannot be found.
77 85 """
78 86 if widget_id not in self.widgets:
79 87 self.log.error("No such widget: %s", widget_id)
80 88 self.log.debug("Current widgets: %s", lazy_keys(self.widgets))
81 89 return
82 90 # call, because we store weakrefs
83 widget = self.widgets[widget_id]()
84 if widget is None:
85 self.log.error("Widget %s has been removed", widget_id)
86 del self.widgets[widget_id]
87 self.log.debug("Current widgets: %s", lazy_keys(self.widgets))
88 return
91 widget = self.widgets[widget_id]
89 92 return widget
90 93
91 94 # Message handlers
92 95
96 def widget_create(self, stream, ident, msg):
97 """Handler for widget_update messages"""
98 content = msg['content']
99 widget_id = content['widget_id']
100 widget_type = content['widget_type']
101 constructor = self.widget_types.get(widget_type, None)
102 if constructor is None:
103 self.log.error("No such widget_type registered: %s", widget_type)
104 return
105 widget = constructor(widget_id=widget_id,
106 shell=self.shell,
107 iopub_socket=self.iopub_socket,
108 _create_data=content['data'],
109 )
110 self.register_widget(widget)
111
93 112 def widget_update(self, stream, ident, msg):
94 113 """Handler for widget_update messages"""
95 114 content = msg['content']
96 115 widget_id = content['widget_id']
97 116 widget = self.get_widget(widget_id)
98 117 if widget is None:
99 118 # no such widget
100 119 return
101 120 widget.handle_update(content['data'])
102 121
103 122 def widget_destroy(self, stream, ident, msg):
104 123 """Handler for widget_destroy messages"""
105 124 content = msg['content']
106 125 widget_id = content['widget_id']
107 126 widget = self.get_widget(widget_id)
108 127 if widget is None:
109 128 # no such widget
110 129 return
111 130 widget.handle_destroy(content['data'])
112 131 del self.widgets[widget_id]
113
132
114 133
115 134 __all__ = ['WidgetManager']
@@ -1,92 +1,113 b''
1 1 """Base class for a Widget"""
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2013 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 import uuid
15 15
16 from IPython.core.getipython import get_ipython
16 17 from IPython.config import LoggingConfigurable
17 18 from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any
18 19
19 20 #-----------------------------------------------------------------------------
20 21 # Code
21 22 #-----------------------------------------------------------------------------
22 23
23 24 class Widget(LoggingConfigurable):
24 25
25 26 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
26 27 def _shell_default(self):
27 28 return get_ipython()
28 29 iopub_socket = Any()
29 30 def _iopub_socket_default(self):
30 31 return self.shell.parent.iopub_socket
31 32 session = Instance('IPython.kernel.zmq.session.Session')
32 33 def _session_default(self):
33 34 if self.shell is None:
34 35 return
35 36 return self.shell.parent.session
36 37
37 38 topic = Bytes()
38 39 def _topic_default(self):
39 40 return ('widget-%s' % self.widget_id).encode('ascii')
40 41
41 42 _destroy_data = Dict(help="data dict, if any, to be included in widget_destroy")
42 43 _create_data = Dict(help="data dict, if any, to be included in widget_create")
43 44
44 45 _destroyed = Bool(False)
45 46 widget_type = Unicode('widget')
46 47 widget_id = Unicode()
47 48 def _widget_id_default(self):
48 49 return uuid.uuid4().hex
49 50
51 primary = Bool(False, help="Am I the primary or secondary Widget?")
52
53 def __init__(self, **kwargs):
54 super(Widget, self).__init__(**kwargs)
55 get_ipython().widget_manager.register_widget(self)
56 if self.primary:
57 # I am primary, create my peer
58 self.create()
59 else:
60 # I am secondary, handle creation
61 self.handle_create(self._create_data)
62
50 63 def _publish_msg(self, msg_type, data=None, **keys):
51 64 """Helper for sending a widget message on IOPub"""
52 65 data = {} if data is None else data
53 66 self.session.send(self.iopub_socket, msg_type,
54 67 dict(data=data, widget_id=self.widget_id, **keys),
55 68 ident=self.topic,
56 69 )
57 70
58 71 def __del__(self):
59 72 """trigger destroy on gc"""
60 73 self.destroy()
61 74
62 75 # publishing messages
63 76
64 def create(self):
77 def create(self, data=None):
65 78 """Create the frontend-side version of this widget"""
66 self._publish_msg('widget_create', self._create_data, widget_type = self.widget_type)
79 if data is None:
80 data = self._create_data
81 self._publish_msg('widget_create', data, widget_type=self.widget_type)
67 82
68 def destroy(self):
83 def destroy(self, data=None):
69 84 """Destroy the frontend-side version of this widget"""
70 85 if self._destroyed:
71 86 # only destroy once
72 87 return
73 self._publish_msg('widget_destroy', self._destroy_data)
88 if data is None:
89 data = self._destroy_data
90 self._publish_msg('widget_destroy', data)
74 91 self._destroyed = True
75 92
76 93 def update(self, data=None):
77 94 """Update the frontend-side version of this widget"""
78 95 self._publish_msg('widget_update', data)
79 96
80 97 # handling of incoming messages
81 98
99 def handle_create(self, data):
100 """Handle a widget_create message"""
101 self.log.debug("handle_create %s", data)
102
82 103 def handle_destroy(self, data):
83 104 """Handle a widget_destroy message"""
84 105 self.log.debug("handle_destroy %s", data)
85 106
86 107 def handle_update(self, data):
87 108 """Handle a widget_update message"""
88 109 self.log.debug("handle_update %s", data)
89 110 self.update_data = data
90 111
91 112
92 113 __all__ = ['Widget']
@@ -1,817 +1,817 b''
1 1 #!/usr/bin/env python
2 2 """A simple interactive kernel that talks to a frontend over 0MQ.
3 3
4 4 Things to do:
5 5
6 6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 7 call set_parent on all the PUB objects with the message about to be executed.
8 8 * Implement random port and security key logic.
9 9 * Implement control messages.
10 10 * Implement event loop and poll version.
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Standard library imports
19 19 import __builtin__
20 20 import sys
21 21 import time
22 22 import traceback
23 23 import logging
24 24 import uuid
25 25
26 26 from datetime import datetime
27 27 from signal import (
28 28 signal, default_int_handler, SIGINT
29 29 )
30 30
31 31 # System library imports
32 32 import zmq
33 33 from zmq.eventloop import ioloop
34 34 from zmq.eventloop.zmqstream import ZMQStream
35 35
36 36 # Local imports
37 37 from IPython.config.configurable import Configurable
38 38 from IPython.core.error import StdinNotImplementedError
39 39 from IPython.core import release
40 40 from IPython.utils import py3compat
41 41 from IPython.utils.jsonutil import json_clean
42 42 from IPython.utils.traitlets import (
43 43 Any, Instance, Float, Dict, List, Set, Integer, Unicode,
44 44 Type
45 45 )
46 46
47 47 from serialize import serialize_object, unpack_apply_message
48 48 from session import Session
49 49 from zmqshell import ZMQInteractiveShell
50 50
51 51
52 52 #-----------------------------------------------------------------------------
53 53 # Main kernel class
54 54 #-----------------------------------------------------------------------------
55 55
56 56 protocol_version = list(release.kernel_protocol_version_info)
57 57 ipython_version = list(release.version_info)
58 58 language_version = list(sys.version_info[:3])
59 59
60 60
61 61 class Kernel(Configurable):
62 62
63 63 #---------------------------------------------------------------------------
64 64 # Kernel interface
65 65 #---------------------------------------------------------------------------
66 66
67 67 # attribute to override with a GUI
68 68 eventloop = Any(None)
69 69 def _eventloop_changed(self, name, old, new):
70 70 """schedule call to eventloop from IOLoop"""
71 71 loop = ioloop.IOLoop.instance()
72 72 loop.add_timeout(time.time()+0.1, self.enter_eventloop)
73 73
74 74 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
75 75 shell_class = Type(ZMQInteractiveShell)
76 76
77 77 session = Instance(Session)
78 78 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
79 79 shell_streams = List()
80 80 control_stream = Instance(ZMQStream)
81 81 iopub_socket = Instance(zmq.Socket)
82 82 stdin_socket = Instance(zmq.Socket)
83 83 log = Instance(logging.Logger)
84 84
85 85 user_module = Any()
86 86 def _user_module_changed(self, name, old, new):
87 87 if self.shell is not None:
88 88 self.shell.user_module = new
89 89
90 90 user_ns = Instance(dict, args=None, allow_none=True)
91 91 def _user_ns_changed(self, name, old, new):
92 92 if self.shell is not None:
93 93 self.shell.user_ns = new
94 94 self.shell.init_user_ns()
95 95
96 96 # identities:
97 97 int_id = Integer(-1)
98 98 ident = Unicode()
99 99
100 100 def _ident_default(self):
101 101 return unicode(uuid.uuid4())
102 102
103 103
104 104 # Private interface
105 105
106 106 # Time to sleep after flushing the stdout/err buffers in each execute
107 107 # cycle. While this introduces a hard limit on the minimal latency of the
108 108 # execute cycle, it helps prevent output synchronization problems for
109 109 # clients.
110 110 # Units are in seconds. The minimum zmq latency on local host is probably
111 111 # ~150 microseconds, set this to 500us for now. We may need to increase it
112 112 # a little if it's not enough after more interactive testing.
113 113 _execute_sleep = Float(0.0005, config=True)
114 114
115 115 # Frequency of the kernel's event loop.
116 116 # Units are in seconds, kernel subclasses for GUI toolkits may need to
117 117 # adapt to milliseconds.
118 118 _poll_interval = Float(0.05, config=True)
119 119
120 120 # If the shutdown was requested over the network, we leave here the
121 121 # necessary reply message so it can be sent by our registered atexit
122 122 # handler. This ensures that the reply is only sent to clients truly at
123 123 # the end of our shutdown process (which happens after the underlying
124 124 # IPython shell's own shutdown).
125 125 _shutdown_message = None
126 126
127 127 # This is a dict of port number that the kernel is listening on. It is set
128 128 # by record_ports and used by connect_request.
129 129 _recorded_ports = Dict()
130 130
131 131 # A reference to the Python builtin 'raw_input' function.
132 132 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
133 133 _sys_raw_input = Any()
134 134 _sys_eval_input = Any()
135 135
136 136 # set of aborted msg_ids
137 137 aborted = Set()
138 138
139 139
140 140 def __init__(self, **kwargs):
141 141 super(Kernel, self).__init__(**kwargs)
142 142
143 143 # Initialize the InteractiveShell subclass
144 144 self.shell = self.shell_class.instance(parent=self,
145 145 profile_dir = self.profile_dir,
146 146 user_module = self.user_module,
147 147 user_ns = self.user_ns,
148 148 )
149 149 self.shell.displayhook.session = self.session
150 150 self.shell.displayhook.pub_socket = self.iopub_socket
151 151 self.shell.displayhook.topic = self._topic('pyout')
152 152 self.shell.display_pub.session = self.session
153 153 self.shell.display_pub.pub_socket = self.iopub_socket
154 154 self.shell.data_pub.session = self.session
155 155 self.shell.data_pub.pub_socket = self.iopub_socket
156 156
157 157 # TMP - hack while developing
158 158 self.shell._reply_content = None
159 159
160 160 # Build dict of handlers for message types
161 161 msg_types = [ 'execute_request', 'complete_request',
162 162 'object_info_request', 'history_request',
163 163 'kernel_info_request',
164 164 'connect_request', 'shutdown_request',
165 165 'apply_request',
166 166 ]
167 167 self.shell_handlers = {}
168 168 for msg_type in msg_types:
169 169 self.shell_handlers[msg_type] = getattr(self, msg_type)
170 170
171 widget_msg_types = [ 'widget_update', 'widget_destroy' ]
171 widget_msg_types = [ 'widget_create', 'widget_update', 'widget_destroy' ]
172 172 widget_manager = self.shell.widget_manager
173 173 for msg_type in widget_msg_types:
174 174 self.shell_handlers[msg_type] = getattr(widget_manager, msg_type)
175 175
176 176 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
177 177 self.control_handlers = {}
178 178 for msg_type in control_msg_types:
179 179 self.control_handlers[msg_type] = getattr(self, msg_type)
180 180
181 181
182 182 def dispatch_control(self, msg):
183 183 """dispatch control requests"""
184 184 idents,msg = self.session.feed_identities(msg, copy=False)
185 185 try:
186 186 msg = self.session.unserialize(msg, content=True, copy=False)
187 187 except:
188 188 self.log.error("Invalid Control Message", exc_info=True)
189 189 return
190 190
191 191 self.log.debug("Control received: %s", msg)
192 192
193 193 header = msg['header']
194 194 msg_id = header['msg_id']
195 195 msg_type = header['msg_type']
196 196
197 197 handler = self.control_handlers.get(msg_type, None)
198 198 if handler is None:
199 199 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
200 200 else:
201 201 try:
202 202 handler(self.control_stream, idents, msg)
203 203 except Exception:
204 204 self.log.error("Exception in control handler:", exc_info=True)
205 205
206 206 def dispatch_shell(self, stream, msg):
207 207 """dispatch shell requests"""
208 208 # flush control requests first
209 209 if self.control_stream:
210 210 self.control_stream.flush()
211 211
212 212 idents,msg = self.session.feed_identities(msg, copy=False)
213 213 try:
214 214 msg = self.session.unserialize(msg, content=True, copy=False)
215 215 except:
216 216 self.log.error("Invalid Message", exc_info=True)
217 217 return
218 218
219 219 header = msg['header']
220 220 msg_id = header['msg_id']
221 221 msg_type = msg['header']['msg_type']
222 222
223 223 # Print some info about this message and leave a '--->' marker, so it's
224 224 # easier to trace visually the message chain when debugging. Each
225 225 # handler prints its message at the end.
226 226 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
227 227 self.log.debug(' Content: %s\n --->\n ', msg['content'])
228 228
229 229 if msg_id in self.aborted:
230 230 self.aborted.remove(msg_id)
231 231 # is it safe to assume a msg_id will not be resubmitted?
232 232 reply_type = msg_type.split('_')[0] + '_reply'
233 233 status = {'status' : 'aborted'}
234 234 md = {'engine' : self.ident}
235 235 md.update(status)
236 236 reply_msg = self.session.send(stream, reply_type, metadata=md,
237 237 content=status, parent=msg, ident=idents)
238 238 return
239 239
240 240 handler = self.shell_handlers.get(msg_type, None)
241 241 if handler is None:
242 242 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
243 243 else:
244 244 # ensure default_int_handler during handler call
245 245 sig = signal(SIGINT, default_int_handler)
246 246 try:
247 247 handler(stream, idents, msg)
248 248 except Exception:
249 249 self.log.error("Exception in message handler:", exc_info=True)
250 250 finally:
251 251 signal(SIGINT, sig)
252 252
253 253 def enter_eventloop(self):
254 254 """enter eventloop"""
255 255 self.log.info("entering eventloop")
256 256 # restore default_int_handler
257 257 signal(SIGINT, default_int_handler)
258 258 while self.eventloop is not None:
259 259 try:
260 260 self.eventloop(self)
261 261 except KeyboardInterrupt:
262 262 # Ctrl-C shouldn't crash the kernel
263 263 self.log.error("KeyboardInterrupt caught in kernel")
264 264 continue
265 265 else:
266 266 # eventloop exited cleanly, this means we should stop (right?)
267 267 self.eventloop = None
268 268 break
269 269 self.log.info("exiting eventloop")
270 270
271 271 def start(self):
272 272 """register dispatchers for streams"""
273 273 self.shell.exit_now = False
274 274 if self.control_stream:
275 275 self.control_stream.on_recv(self.dispatch_control, copy=False)
276 276
277 277 def make_dispatcher(stream):
278 278 def dispatcher(msg):
279 279 return self.dispatch_shell(stream, msg)
280 280 return dispatcher
281 281
282 282 for s in self.shell_streams:
283 283 s.on_recv(make_dispatcher(s), copy=False)
284 284
285 285 # publish idle status
286 286 self._publish_status('starting')
287 287
288 288 def do_one_iteration(self):
289 289 """step eventloop just once"""
290 290 if self.control_stream:
291 291 self.control_stream.flush()
292 292 for stream in self.shell_streams:
293 293 # handle at most one request per iteration
294 294 stream.flush(zmq.POLLIN, 1)
295 295 stream.flush(zmq.POLLOUT)
296 296
297 297
298 298 def record_ports(self, ports):
299 299 """Record the ports that this kernel is using.
300 300
301 301 The creator of the Kernel instance must call this methods if they
302 302 want the :meth:`connect_request` method to return the port numbers.
303 303 """
304 304 self._recorded_ports = ports
305 305
306 306 #---------------------------------------------------------------------------
307 307 # Kernel request handlers
308 308 #---------------------------------------------------------------------------
309 309
310 310 def _make_metadata(self, other=None):
311 311 """init metadata dict, for execute/apply_reply"""
312 312 new_md = {
313 313 'dependencies_met' : True,
314 314 'engine' : self.ident,
315 315 'started': datetime.now(),
316 316 }
317 317 if other:
318 318 new_md.update(other)
319 319 return new_md
320 320
321 321 def _publish_pyin(self, code, parent, execution_count):
322 322 """Publish the code request on the pyin stream."""
323 323
324 324 self.session.send(self.iopub_socket, u'pyin',
325 325 {u'code':code, u'execution_count': execution_count},
326 326 parent=parent, ident=self._topic('pyin')
327 327 )
328 328
329 329 def _publish_status(self, status, parent=None):
330 330 """send status (busy/idle) on IOPub"""
331 331 self.session.send(self.iopub_socket,
332 332 u'status',
333 333 {u'execution_state': status},
334 334 parent=parent,
335 335 ident=self._topic('status'),
336 336 )
337 337
338 338
339 339 def execute_request(self, stream, ident, parent):
340 340 """handle an execute_request"""
341 341
342 342 self._publish_status(u'busy', parent)
343 343
344 344 try:
345 345 content = parent[u'content']
346 346 code = content[u'code']
347 347 silent = content[u'silent']
348 348 store_history = content.get(u'store_history', not silent)
349 349 except:
350 350 self.log.error("Got bad msg: ")
351 351 self.log.error("%s", parent)
352 352 return
353 353
354 354 md = self._make_metadata(parent['metadata'])
355 355
356 356 shell = self.shell # we'll need this a lot here
357 357
358 358 # Replace raw_input. Note that is not sufficient to replace
359 359 # raw_input in the user namespace.
360 360 if content.get('allow_stdin', False):
361 361 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
362 362 input = lambda prompt='': eval(raw_input(prompt))
363 363 else:
364 364 raw_input = input = lambda prompt='' : self._no_raw_input()
365 365
366 366 if py3compat.PY3:
367 367 self._sys_raw_input = __builtin__.input
368 368 __builtin__.input = raw_input
369 369 else:
370 370 self._sys_raw_input = __builtin__.raw_input
371 371 self._sys_eval_input = __builtin__.input
372 372 __builtin__.raw_input = raw_input
373 373 __builtin__.input = input
374 374
375 375 # Set the parent message of the display hook and out streams.
376 376 shell.displayhook.set_parent(parent)
377 377 shell.display_pub.set_parent(parent)
378 378 shell.data_pub.set_parent(parent)
379 379 try:
380 380 sys.stdout.set_parent(parent)
381 381 except AttributeError:
382 382 pass
383 383 try:
384 384 sys.stderr.set_parent(parent)
385 385 except AttributeError:
386 386 pass
387 387
388 388 # Re-broadcast our input for the benefit of listening clients, and
389 389 # start computing output
390 390 if not silent:
391 391 self._publish_pyin(code, parent, shell.execution_count)
392 392
393 393 reply_content = {}
394 394 try:
395 395 # FIXME: the shell calls the exception handler itself.
396 396 shell.run_cell(code, store_history=store_history, silent=silent)
397 397 except:
398 398 status = u'error'
399 399 # FIXME: this code right now isn't being used yet by default,
400 400 # because the run_cell() call above directly fires off exception
401 401 # reporting. This code, therefore, is only active in the scenario
402 402 # where runlines itself has an unhandled exception. We need to
403 403 # uniformize this, for all exception construction to come from a
404 404 # single location in the codbase.
405 405 etype, evalue, tb = sys.exc_info()
406 406 tb_list = traceback.format_exception(etype, evalue, tb)
407 407 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
408 408 else:
409 409 status = u'ok'
410 410 finally:
411 411 # Restore raw_input.
412 412 if py3compat.PY3:
413 413 __builtin__.input = self._sys_raw_input
414 414 else:
415 415 __builtin__.raw_input = self._sys_raw_input
416 416 __builtin__.input = self._sys_eval_input
417 417
418 418 reply_content[u'status'] = status
419 419
420 420 # Return the execution counter so clients can display prompts
421 421 reply_content['execution_count'] = shell.execution_count - 1
422 422
423 423 # FIXME - fish exception info out of shell, possibly left there by
424 424 # runlines. We'll need to clean up this logic later.
425 425 if shell._reply_content is not None:
426 426 reply_content.update(shell._reply_content)
427 427 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
428 428 reply_content['engine_info'] = e_info
429 429 # reset after use
430 430 shell._reply_content = None
431 431
432 432 if 'traceback' in reply_content:
433 433 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
434 434
435 435
436 436 # At this point, we can tell whether the main code execution succeeded
437 437 # or not. If it did, we proceed to evaluate user_variables/expressions
438 438 if reply_content['status'] == 'ok':
439 439 reply_content[u'user_variables'] = \
440 440 shell.user_variables(content.get(u'user_variables', []))
441 441 reply_content[u'user_expressions'] = \
442 442 shell.user_expressions(content.get(u'user_expressions', {}))
443 443 else:
444 444 # If there was an error, don't even try to compute variables or
445 445 # expressions
446 446 reply_content[u'user_variables'] = {}
447 447 reply_content[u'user_expressions'] = {}
448 448
449 449 # Payloads should be retrieved regardless of outcome, so we can both
450 450 # recover partial output (that could have been generated early in a
451 451 # block, before an error) and clear the payload system always.
452 452 reply_content[u'payload'] = shell.payload_manager.read_payload()
453 453 # Be agressive about clearing the payload because we don't want
454 454 # it to sit in memory until the next execute_request comes in.
455 455 shell.payload_manager.clear_payload()
456 456
457 457 # Flush output before sending the reply.
458 458 sys.stdout.flush()
459 459 sys.stderr.flush()
460 460 # FIXME: on rare occasions, the flush doesn't seem to make it to the
461 461 # clients... This seems to mitigate the problem, but we definitely need
462 462 # to better understand what's going on.
463 463 if self._execute_sleep:
464 464 time.sleep(self._execute_sleep)
465 465
466 466 # Send the reply.
467 467 reply_content = json_clean(reply_content)
468 468
469 469 md['status'] = reply_content['status']
470 470 if reply_content['status'] == 'error' and \
471 471 reply_content['ename'] == 'UnmetDependency':
472 472 md['dependencies_met'] = False
473 473
474 474 reply_msg = self.session.send(stream, u'execute_reply',
475 475 reply_content, parent, metadata=md,
476 476 ident=ident)
477 477
478 478 self.log.debug("%s", reply_msg)
479 479
480 480 if not silent and reply_msg['content']['status'] == u'error':
481 481 self._abort_queues()
482 482
483 483 self._publish_status(u'idle', parent)
484 484
485 485 def complete_request(self, stream, ident, parent):
486 486 txt, matches = self._complete(parent)
487 487 matches = {'matches' : matches,
488 488 'matched_text' : txt,
489 489 'status' : 'ok'}
490 490 matches = json_clean(matches)
491 491 completion_msg = self.session.send(stream, 'complete_reply',
492 492 matches, parent, ident)
493 493 self.log.debug("%s", completion_msg)
494 494
495 495 def object_info_request(self, stream, ident, parent):
496 496 content = parent['content']
497 497 object_info = self.shell.object_inspect(content['oname'],
498 498 detail_level = content.get('detail_level', 0)
499 499 )
500 500 # Before we send this object over, we scrub it for JSON usage
501 501 oinfo = json_clean(object_info)
502 502 msg = self.session.send(stream, 'object_info_reply',
503 503 oinfo, parent, ident)
504 504 self.log.debug("%s", msg)
505 505
506 506 def history_request(self, stream, ident, parent):
507 507 # We need to pull these out, as passing **kwargs doesn't work with
508 508 # unicode keys before Python 2.6.5.
509 509 hist_access_type = parent['content']['hist_access_type']
510 510 raw = parent['content']['raw']
511 511 output = parent['content']['output']
512 512 if hist_access_type == 'tail':
513 513 n = parent['content']['n']
514 514 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
515 515 include_latest=True)
516 516
517 517 elif hist_access_type == 'range':
518 518 session = parent['content']['session']
519 519 start = parent['content']['start']
520 520 stop = parent['content']['stop']
521 521 hist = self.shell.history_manager.get_range(session, start, stop,
522 522 raw=raw, output=output)
523 523
524 524 elif hist_access_type == 'search':
525 525 n = parent['content'].get('n')
526 526 unique = parent['content'].get('unique', False)
527 527 pattern = parent['content']['pattern']
528 528 hist = self.shell.history_manager.search(
529 529 pattern, raw=raw, output=output, n=n, unique=unique)
530 530
531 531 else:
532 532 hist = []
533 533 hist = list(hist)
534 534 content = {'history' : hist}
535 535 content = json_clean(content)
536 536 msg = self.session.send(stream, 'history_reply',
537 537 content, parent, ident)
538 538 self.log.debug("Sending history reply with %i entries", len(hist))
539 539
540 540 def connect_request(self, stream, ident, parent):
541 541 if self._recorded_ports is not None:
542 542 content = self._recorded_ports.copy()
543 543 else:
544 544 content = {}
545 545 msg = self.session.send(stream, 'connect_reply',
546 546 content, parent, ident)
547 547 self.log.debug("%s", msg)
548 548
549 549 def kernel_info_request(self, stream, ident, parent):
550 550 vinfo = {
551 551 'protocol_version': protocol_version,
552 552 'ipython_version': ipython_version,
553 553 'language_version': language_version,
554 554 'language': 'python',
555 555 }
556 556 msg = self.session.send(stream, 'kernel_info_reply',
557 557 vinfo, parent, ident)
558 558 self.log.debug("%s", msg)
559 559
560 560 def shutdown_request(self, stream, ident, parent):
561 561 self.shell.exit_now = True
562 562 content = dict(status='ok')
563 563 content.update(parent['content'])
564 564 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
565 565 # same content, but different msg_id for broadcasting on IOPub
566 566 self._shutdown_message = self.session.msg(u'shutdown_reply',
567 567 content, parent
568 568 )
569 569
570 570 self._at_shutdown()
571 571 # call sys.exit after a short delay
572 572 loop = ioloop.IOLoop.instance()
573 573 loop.add_timeout(time.time()+0.1, loop.stop)
574 574
575 575 #---------------------------------------------------------------------------
576 576 # Engine methods
577 577 #---------------------------------------------------------------------------
578 578
579 579 def apply_request(self, stream, ident, parent):
580 580 try:
581 581 content = parent[u'content']
582 582 bufs = parent[u'buffers']
583 583 msg_id = parent['header']['msg_id']
584 584 except:
585 585 self.log.error("Got bad msg: %s", parent, exc_info=True)
586 586 return
587 587
588 588 self._publish_status(u'busy', parent)
589 589
590 590 # Set the parent message of the display hook and out streams.
591 591 shell = self.shell
592 592 shell.displayhook.set_parent(parent)
593 593 shell.display_pub.set_parent(parent)
594 594 shell.data_pub.set_parent(parent)
595 595 try:
596 596 sys.stdout.set_parent(parent)
597 597 except AttributeError:
598 598 pass
599 599 try:
600 600 sys.stderr.set_parent(parent)
601 601 except AttributeError:
602 602 pass
603 603
604 604 # pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
605 605 # self.iopub_socket.send(pyin_msg)
606 606 # self.session.send(self.iopub_socket, u'pyin', {u'code':code},parent=parent)
607 607 md = self._make_metadata(parent['metadata'])
608 608 try:
609 609 working = shell.user_ns
610 610
611 611 prefix = "_"+str(msg_id).replace("-","")+"_"
612 612
613 613 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
614 614
615 615 fname = getattr(f, '__name__', 'f')
616 616
617 617 fname = prefix+"f"
618 618 argname = prefix+"args"
619 619 kwargname = prefix+"kwargs"
620 620 resultname = prefix+"result"
621 621
622 622 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
623 623 # print ns
624 624 working.update(ns)
625 625 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
626 626 try:
627 627 exec code in shell.user_global_ns, shell.user_ns
628 628 result = working.get(resultname)
629 629 finally:
630 630 for key in ns.iterkeys():
631 631 working.pop(key)
632 632
633 633 result_buf = serialize_object(result,
634 634 buffer_threshold=self.session.buffer_threshold,
635 635 item_threshold=self.session.item_threshold,
636 636 )
637 637
638 638 except:
639 639 # invoke IPython traceback formatting
640 640 shell.showtraceback()
641 641 # FIXME - fish exception info out of shell, possibly left there by
642 642 # run_code. We'll need to clean up this logic later.
643 643 reply_content = {}
644 644 if shell._reply_content is not None:
645 645 reply_content.update(shell._reply_content)
646 646 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
647 647 reply_content['engine_info'] = e_info
648 648 # reset after use
649 649 shell._reply_content = None
650 650
651 651 self.session.send(self.iopub_socket, u'pyerr', reply_content, parent=parent,
652 652 ident=self._topic('pyerr'))
653 653 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
654 654 result_buf = []
655 655
656 656 if reply_content['ename'] == 'UnmetDependency':
657 657 md['dependencies_met'] = False
658 658 else:
659 659 reply_content = {'status' : 'ok'}
660 660
661 661 # put 'ok'/'error' status in header, for scheduler introspection:
662 662 md['status'] = reply_content['status']
663 663
664 664 # flush i/o
665 665 sys.stdout.flush()
666 666 sys.stderr.flush()
667 667
668 668 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
669 669 parent=parent, ident=ident,buffers=result_buf, metadata=md)
670 670
671 671 self._publish_status(u'idle', parent)
672 672
673 673 #---------------------------------------------------------------------------
674 674 # Control messages
675 675 #---------------------------------------------------------------------------
676 676
677 677 def abort_request(self, stream, ident, parent):
678 678 """abort a specifig msg by id"""
679 679 msg_ids = parent['content'].get('msg_ids', None)
680 680 if isinstance(msg_ids, basestring):
681 681 msg_ids = [msg_ids]
682 682 if not msg_ids:
683 683 self.abort_queues()
684 684 for mid in msg_ids:
685 685 self.aborted.add(str(mid))
686 686
687 687 content = dict(status='ok')
688 688 reply_msg = self.session.send(stream, 'abort_reply', content=content,
689 689 parent=parent, ident=ident)
690 690 self.log.debug("%s", reply_msg)
691 691
692 692 def clear_request(self, stream, idents, parent):
693 693 """Clear our namespace."""
694 694 self.shell.reset(False)
695 695 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
696 696 content = dict(status='ok'))
697 697
698 698
699 699 #---------------------------------------------------------------------------
700 700 # Protected interface
701 701 #---------------------------------------------------------------------------
702 702
703 703 def _wrap_exception(self, method=None):
704 704 # import here, because _wrap_exception is only used in parallel,
705 705 # and parallel has higher min pyzmq version
706 706 from IPython.parallel.error import wrap_exception
707 707 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
708 708 content = wrap_exception(e_info)
709 709 return content
710 710
711 711 def _topic(self, topic):
712 712 """prefixed topic for IOPub messages"""
713 713 if self.int_id >= 0:
714 714 base = "engine.%i" % self.int_id
715 715 else:
716 716 base = "kernel.%s" % self.ident
717 717
718 718 return py3compat.cast_bytes("%s.%s" % (base, topic))
719 719
720 720 def _abort_queues(self):
721 721 for stream in self.shell_streams:
722 722 if stream:
723 723 self._abort_queue(stream)
724 724
725 725 def _abort_queue(self, stream):
726 726 poller = zmq.Poller()
727 727 poller.register(stream.socket, zmq.POLLIN)
728 728 while True:
729 729 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
730 730 if msg is None:
731 731 return
732 732
733 733 self.log.info("Aborting:")
734 734 self.log.info("%s", msg)
735 735 msg_type = msg['header']['msg_type']
736 736 reply_type = msg_type.split('_')[0] + '_reply'
737 737
738 738 status = {'status' : 'aborted'}
739 739 md = {'engine' : self.ident}
740 740 md.update(status)
741 741 reply_msg = self.session.send(stream, reply_type, metadata=md,
742 742 content=status, parent=msg, ident=idents)
743 743 self.log.debug("%s", reply_msg)
744 744 # We need to wait a bit for requests to come in. This can probably
745 745 # be set shorter for true asynchronous clients.
746 746 poller.poll(50)
747 747
748 748
749 749 def _no_raw_input(self):
750 750 """Raise StdinNotImplentedError if active frontend doesn't support
751 751 stdin."""
752 752 raise StdinNotImplementedError("raw_input was called, but this "
753 753 "frontend does not support stdin.")
754 754
755 755 def _raw_input(self, prompt, ident, parent):
756 756 # Flush output before making the request.
757 757 sys.stderr.flush()
758 758 sys.stdout.flush()
759 759 # flush the stdin socket, to purge stale replies
760 760 while True:
761 761 try:
762 762 self.stdin_socket.recv_multipart(zmq.NOBLOCK)
763 763 except zmq.ZMQError as e:
764 764 if e.errno == zmq.EAGAIN:
765 765 break
766 766 else:
767 767 raise
768 768
769 769 # Send the input request.
770 770 content = json_clean(dict(prompt=prompt))
771 771 self.session.send(self.stdin_socket, u'input_request', content, parent,
772 772 ident=ident)
773 773
774 774 # Await a response.
775 775 while True:
776 776 try:
777 777 ident, reply = self.session.recv(self.stdin_socket, 0)
778 778 except Exception:
779 779 self.log.warn("Invalid Message:", exc_info=True)
780 780 except KeyboardInterrupt:
781 781 # re-raise KeyboardInterrupt, to truncate traceback
782 782 raise KeyboardInterrupt
783 783 else:
784 784 break
785 785 try:
786 786 value = py3compat.unicode_to_str(reply['content']['value'])
787 787 except:
788 788 self.log.error("Got bad raw_input reply: ")
789 789 self.log.error("%s", parent)
790 790 value = ''
791 791 if value == '\x04':
792 792 # EOF
793 793 raise EOFError
794 794 return value
795 795
796 796 def _complete(self, msg):
797 797 c = msg['content']
798 798 try:
799 799 cpos = int(c['cursor_pos'])
800 800 except:
801 801 # If we don't get something that we can convert to an integer, at
802 802 # least attempt the completion guessing the cursor is at the end of
803 803 # the text, if there's any, and otherwise of the line
804 804 cpos = len(c['text'])
805 805 if cpos==0:
806 806 cpos = len(c['line'])
807 807 return self.shell.complete(c['text'], c['line'], cpos)
808 808
809 809 def _at_shutdown(self):
810 810 """Actions taken at shutdown by the kernel, called by python's atexit.
811 811 """
812 812 # io.rprint("Kernel at_shutdown") # dbg
813 813 if self._shutdown_message is not None:
814 814 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
815 815 self.log.debug("%s", self._shutdown_message)
816 816 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
817 817
General Comments 0
You need to be logged in to leave comments. Login now