Show More
@@ -0,0 +1,115 b'' | |||
|
1 | """Base class to manage widgets""" | |
|
2 | ||
|
3 | #----------------------------------------------------------------------------- | |
|
4 | # Copyright (C) 2013 The IPython Development Team | |
|
5 | # | |
|
6 | # Distributed under the terms of the BSD License. The full license is in | |
|
7 | # the file COPYING, distributed as part of this software. | |
|
8 | #----------------------------------------------------------------------------- | |
|
9 | ||
|
10 | #----------------------------------------------------------------------------- | |
|
11 | # Imports | |
|
12 | #----------------------------------------------------------------------------- | |
|
13 | ||
|
14 | from weakref import ref | |
|
15 | ||
|
16 | from IPython.config import LoggingConfigurable | |
|
17 | from IPython.core.prompts import LazyEvaluate | |
|
18 | from IPython.core.getipython import get_ipython | |
|
19 | from IPython.utils.traitlets import Instance, Unicode, Dict, Any | |
|
20 | ||
|
21 | #----------------------------------------------------------------------------- | |
|
22 | # Code | |
|
23 | #----------------------------------------------------------------------------- | |
|
24 | ||
|
25 | def lazy_keys(dikt): | |
|
26 | """Return lazy-evaluated string representation of a dictionary's keys | |
|
27 | ||
|
28 | Key list is only constructed if it will actually be used. | |
|
29 | Used for debug-logging. | |
|
30 | """ | |
|
31 | return LazyEvaluate(lambda d: list(d.keys())) | |
|
32 | ||
|
33 | class WidgetManager(LoggingConfigurable): | |
|
34 | """Manager for Widgets in the Kernel""" | |
|
35 | ||
|
36 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') | |
|
37 | def _shell_default(self): | |
|
38 | return get_ipython() | |
|
39 | iopub_socket = Any() | |
|
40 | def _iopub_socket_default(self): | |
|
41 | return self.shell.parent.iopub_socket | |
|
42 | session = Instance('IPython.kernel.zmq.session.Session') | |
|
43 | def _session_default(self): | |
|
44 | if self.shell is None: | |
|
45 | return | |
|
46 | return self.shell.parent.session | |
|
47 | ||
|
48 | widgets = Dict() | |
|
49 | ||
|
50 | # Public APIs | |
|
51 | ||
|
52 | def register_widget(self, widget): | |
|
53 | """Register a new widget""" | |
|
54 | self.widgets[widget.widget_id] = ref(widget) | |
|
55 | widget.shell = self.shell | |
|
56 | widget.iopub_socket = self.iopub_socket | |
|
57 | widget.create() | |
|
58 | return widget.widget_id | |
|
59 | ||
|
60 | def unregister_widget(self, widget_id): | |
|
61 | """Unregister a widget, and destroy its counterpart""" | |
|
62 | # 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 | |
|
68 | widget.destroy() | |
|
69 | ||
|
70 | def get_widget(self, widget_id): | |
|
71 | """Get a widget with a particular id | |
|
72 | ||
|
73 | Returns the widget if found, otherwise None. | |
|
74 | ||
|
75 | This will not raise an error, | |
|
76 | it will log messages if the widget cannot be found. | |
|
77 | """ | |
|
78 | if widget_id not in self.widgets: | |
|
79 | self.log.error("No such widget: %s", widget_id) | |
|
80 | self.log.debug("Current widgets: %s", lazy_keys(self.widgets)) | |
|
81 | return | |
|
82 | # 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 | |
|
89 | return widget | |
|
90 | ||
|
91 | # Message handlers | |
|
92 | ||
|
93 | def widget_update(self, stream, ident, msg): | |
|
94 | """Handler for widget_update messages""" | |
|
95 | content = msg['content'] | |
|
96 | widget_id = content['widget_id'] | |
|
97 | widget = self.get_widget(widget_id) | |
|
98 | if widget is None: | |
|
99 | # no such widget | |
|
100 | return | |
|
101 | widget.handle_update(content['data']) | |
|
102 | ||
|
103 | def widget_destroy(self, stream, ident, msg): | |
|
104 | """Handler for widget_destroy messages""" | |
|
105 | content = msg['content'] | |
|
106 | widget_id = content['widget_id'] | |
|
107 | widget = self.get_widget(widget_id) | |
|
108 | if widget is None: | |
|
109 | # no such widget | |
|
110 | return | |
|
111 | widget.handle_destroy(content['data']) | |
|
112 | del self.widgets[widget_id] | |
|
113 | ||
|
114 | ||
|
115 | __all__ = ['WidgetManager'] |
@@ -0,0 +1,92 b'' | |||
|
1 | """Base class for a Widget""" | |
|
2 | ||
|
3 | #----------------------------------------------------------------------------- | |
|
4 | # Copyright (C) 2013 The IPython Development Team | |
|
5 | # | |
|
6 | # Distributed under the terms of the BSD License. The full license is in | |
|
7 | # the file COPYING, distributed as part of this software. | |
|
8 | #----------------------------------------------------------------------------- | |
|
9 | ||
|
10 | #----------------------------------------------------------------------------- | |
|
11 | # Imports | |
|
12 | #----------------------------------------------------------------------------- | |
|
13 | ||
|
14 | import uuid | |
|
15 | ||
|
16 | from IPython.config import LoggingConfigurable | |
|
17 | from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any | |
|
18 | ||
|
19 | #----------------------------------------------------------------------------- | |
|
20 | # Code | |
|
21 | #----------------------------------------------------------------------------- | |
|
22 | ||
|
23 | class Widget(LoggingConfigurable): | |
|
24 | ||
|
25 | shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') | |
|
26 | def _shell_default(self): | |
|
27 | return get_ipython() | |
|
28 | iopub_socket = Any() | |
|
29 | def _iopub_socket_default(self): | |
|
30 | return self.shell.parent.iopub_socket | |
|
31 | session = Instance('IPython.kernel.zmq.session.Session') | |
|
32 | def _session_default(self): | |
|
33 | if self.shell is None: | |
|
34 | return | |
|
35 | return self.shell.parent.session | |
|
36 | ||
|
37 | topic = Bytes() | |
|
38 | def _topic_default(self): | |
|
39 | return ('widget-%s' % self.widget_id).encode('ascii') | |
|
40 | ||
|
41 | _destroy_data = Dict(help="data dict, if any, to be included in widget_destroy") | |
|
42 | _create_data = Dict(help="data dict, if any, to be included in widget_create") | |
|
43 | ||
|
44 | _destroyed = Bool(False) | |
|
45 | widget_type = Unicode('widget') | |
|
46 | widget_id = Unicode() | |
|
47 | def _widget_id_default(self): | |
|
48 | return uuid.uuid4().hex | |
|
49 | ||
|
50 | def _publish_msg(self, msg_type, data=None, **keys): | |
|
51 | """Helper for sending a widget message on IOPub""" | |
|
52 | data = {} if data is None else data | |
|
53 | self.session.send(self.iopub_socket, msg_type, | |
|
54 | dict(data=data, widget_id=self.widget_id, **keys), | |
|
55 | ident=self.topic, | |
|
56 | ) | |
|
57 | ||
|
58 | def __del__(self): | |
|
59 | """trigger destroy on gc""" | |
|
60 | self.destroy() | |
|
61 | ||
|
62 | # publishing messages | |
|
63 | ||
|
64 | def create(self): | |
|
65 | """Create the frontend-side version of this widget""" | |
|
66 | self._publish_msg('widget_create', self._create_data, widget_type = self.widget_type) | |
|
67 | ||
|
68 | def destroy(self): | |
|
69 | """Destroy the frontend-side version of this widget""" | |
|
70 | if self._destroyed: | |
|
71 | # only destroy once | |
|
72 | return | |
|
73 | self._publish_msg('widget_destroy', self._destroy_data) | |
|
74 | self._destroyed = True | |
|
75 | ||
|
76 | def update(self, data=None): | |
|
77 | """Update the frontend-side version of this widget""" | |
|
78 | self._publish_msg('widget_update', data) | |
|
79 | ||
|
80 | # handling of incoming messages | |
|
81 | ||
|
82 | def handle_destroy(self, data): | |
|
83 | """Handle a widget_destroy message""" | |
|
84 | self.log.debug("handle_destroy %s", data) | |
|
85 | ||
|
86 | def handle_update(self, data): | |
|
87 | """Handle a widget_update message""" | |
|
88 | self.log.debug("handle_update %s", data) | |
|
89 | self.update_data = data | |
|
90 | ||
|
91 | ||
|
92 | __all__ = ['Widget'] |
@@ -507,6 +507,7 b' class InteractiveShell(SingletonConfigurable):' | |||
|
507 | 507 | self.init_pdb() |
|
508 | 508 | self.init_extension_manager() |
|
509 | 509 | self.init_payload() |
|
510 | self.init_widgets() | |
|
510 | 511 | self.hooks.late_startup_hook() |
|
511 | 512 | atexit.register(self.atexit_operations) |
|
512 | 513 | |
@@ -2319,6 +2320,14 b' class InteractiveShell(SingletonConfigurable):' | |||
|
2319 | 2320 | self.configurables.append(self.payload_manager) |
|
2320 | 2321 | |
|
2321 | 2322 | #------------------------------------------------------------------------- |
|
2323 | # Things related to widgets | |
|
2324 | #------------------------------------------------------------------------- | |
|
2325 | ||
|
2326 | def init_widgets(self): | |
|
2327 | # not implemented in the base class | |
|
2328 | pass | |
|
2329 | ||
|
2330 | #------------------------------------------------------------------------- | |
|
2322 | 2331 | # Things related to the prefilter |
|
2323 | 2332 | #------------------------------------------------------------------------- |
|
2324 | 2333 |
@@ -168,11 +168,17 b' class Kernel(Configurable):' | |||
|
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' ] | |
|
172 | widget_manager = self.shell.widget_manager | |
|
173 | for msg_type in widget_msg_types: | |
|
174 | self.shell_handlers[msg_type] = getattr(widget_manager, msg_type) | |
|
175 | ||
|
171 | 176 | control_msg_types = msg_types + [ 'clear_request', 'abort_request' ] |
|
172 | 177 | self.control_handlers = {} |
|
173 | 178 | for msg_type in control_msg_types: |
|
174 | 179 | self.control_handlers[msg_type] = getattr(self, msg_type) |
|
175 | 180 | |
|
181 | ||
|
176 | 182 | def dispatch_control(self, msg): |
|
177 | 183 | """dispatch control requests""" |
|
178 | 184 | idents,msg = self.session.feed_identities(msg, copy=False) |
@@ -49,6 +49,7 b' from IPython.utils.warn import error' | |||
|
49 | 49 | from IPython.kernel.zmq.displayhook import ZMQShellDisplayHook |
|
50 | 50 | from IPython.kernel.zmq.datapub import ZMQDataPublisher |
|
51 | 51 | from IPython.kernel.zmq.session import extract_header |
|
52 | from IPython.kernel.widgets import WidgetManager | |
|
52 | 53 | from session import Session |
|
53 | 54 | |
|
54 | 55 | #----------------------------------------------------------------------------- |
@@ -594,6 +595,9 b' class ZMQInteractiveShell(InteractiveShell):' | |||
|
594 | 595 | self.register_magics(KernelMagics) |
|
595 | 596 | self.magics_manager.register_alias('ed', 'edit') |
|
596 | 597 | |
|
598 | def init_widgets(self): | |
|
599 | self.widget_manager = WidgetManager(shell=self, parent=self) | |
|
600 | self.configurables.append(self.widget_manager) | |
|
597 | 601 | |
|
598 | 602 | |
|
599 | 603 | InteractiveShellABC.register(ZMQInteractiveShell) |
General Comments 0
You need to be logged in to leave comments.
Login now