##// END OF EJS Templates
Handle some tricky Comm lifecycle issues...
Jason Grout -
Show More
@@ -1,153 +1,155 b''
1 """Base class for a Comm"""
1 """Base class for a Comm"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import uuid
6 import uuid
7
7
8 from IPython.config import LoggingConfigurable
8 from IPython.config import LoggingConfigurable
9 from IPython.kernel.zmq.kernelbase import Kernel
9 from IPython.kernel.zmq.kernelbase import Kernel
10
10
11 from IPython.utils.jsonutil import json_clean
11 from IPython.utils.jsonutil import json_clean
12 from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any
12 from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any
13
13
14
14
15 class Comm(LoggingConfigurable):
15 class Comm(LoggingConfigurable):
16
16
17 # If this is instantiated by a non-IPython kernel, shell will be None
17 # If this is instantiated by a non-IPython kernel, shell will be None
18 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
18 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
19 allow_none=True)
19 allow_none=True)
20 kernel = Instance('IPython.kernel.zmq.kernelbase.Kernel')
20 kernel = Instance('IPython.kernel.zmq.kernelbase.Kernel')
21 def _kernel_default(self):
21 def _kernel_default(self):
22 if Kernel.initialized():
22 if Kernel.initialized():
23 return Kernel.instance()
23 return Kernel.instance()
24
24
25 iopub_socket = Any()
25 iopub_socket = Any()
26 def _iopub_socket_default(self):
26 def _iopub_socket_default(self):
27 return self.kernel.iopub_socket
27 return self.kernel.iopub_socket
28 session = Instance('IPython.kernel.zmq.session.Session')
28 session = Instance('IPython.kernel.zmq.session.Session')
29 def _session_default(self):
29 def _session_default(self):
30 if self.kernel is not None:
30 if self.kernel is not None:
31 return self.kernel.session
31 return self.kernel.session
32
32
33 target_name = Unicode('comm')
33 target_name = Unicode('comm')
34
34
35 topic = Bytes()
35 topic = Bytes()
36 def _topic_default(self):
36 def _topic_default(self):
37 return ('comm-%s' % self.comm_id).encode('ascii')
37 return ('comm-%s' % self.comm_id).encode('ascii')
38
38
39 _open_data = Dict(help="data dict, if any, to be included in comm_open")
39 _open_data = Dict(help="data dict, if any, to be included in comm_open")
40 _close_data = Dict(help="data dict, if any, to be included in comm_close")
40 _close_data = Dict(help="data dict, if any, to be included in comm_close")
41
41
42 _msg_callback = Any()
42 _msg_callback = Any()
43 _close_callback = Any()
43 _close_callback = Any()
44
44
45 _closed = Bool(False)
45 _closed = Bool(True)
46 comm_id = Unicode()
46 comm_id = Unicode()
47 def _comm_id_default(self):
47 def _comm_id_default(self):
48 return uuid.uuid4().hex
48 return uuid.uuid4().hex
49
49
50 primary = Bool(True, help="Am I the primary or secondary Comm?")
50 primary = Bool(True, help="Am I the primary or secondary Comm?")
51
51
52 def __init__(self, target_name='', data=None, **kwargs):
52 def __init__(self, target_name='', data=None, **kwargs):
53 if target_name:
53 if target_name:
54 kwargs['target_name'] = target_name
54 kwargs['target_name'] = target_name
55 super(Comm, self).__init__(**kwargs)
55 super(Comm, self).__init__(**kwargs)
56 if self.primary:
56 if self.primary:
57 # I am primary, open my peer.
57 # I am primary, open my peer.
58 self.open(data)
58 self.open(data)
59 else:
60 self._closed = False
59
61
60 def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys):
62 def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys):
61 """Helper for sending a comm message on IOPub"""
63 """Helper for sending a comm message on IOPub"""
62 data = {} if data is None else data
64 data = {} if data is None else data
63 metadata = {} if metadata is None else metadata
65 metadata = {} if metadata is None else metadata
64 content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))
66 content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))
65 self.session.send(self.iopub_socket, msg_type,
67 self.session.send(self.iopub_socket, msg_type,
66 content,
68 content,
67 metadata=json_clean(metadata),
69 metadata=json_clean(metadata),
68 parent=self.kernel._parent_header,
70 parent=self.kernel._parent_header,
69 ident=self.topic,
71 ident=self.topic,
70 buffers=buffers,
72 buffers=buffers,
71 )
73 )
72
74
73 def __del__(self):
75 def __del__(self):
74 """trigger close on gc"""
76 """trigger close on gc"""
75 self.close()
77 self.close()
76
78
77 # publishing messages
79 # publishing messages
78
80
79 def open(self, data=None, metadata=None, buffers=None):
81 def open(self, data=None, metadata=None, buffers=None):
80 """Open the frontend-side version of this comm"""
82 """Open the frontend-side version of this comm"""
81 if data is None:
83 if data is None:
82 data = self._open_data
84 data = self._open_data
83 comm_manager = getattr(self.kernel, 'comm_manager', None)
85 comm_manager = getattr(self.kernel, 'comm_manager', None)
84 if comm_manager is None:
86 if comm_manager is None:
85 raise RuntimeError("Comms cannot be opened without a kernel "
87 raise RuntimeError("Comms cannot be opened without a kernel "
86 "and a comm_manager attached to that kernel.")
88 "and a comm_manager attached to that kernel.")
87
89
88 comm_manager.register_comm(self)
90 comm_manager.register_comm(self)
89 self._closed = False
90 self._publish_msg('comm_open',
91 self._publish_msg('comm_open',
91 data=data, metadata=metadata, buffers=buffers,
92 data=data, metadata=metadata, buffers=buffers,
92 target_name=self.target_name,
93 target_name=self.target_name,
93 )
94 )
95 self._closed = False
94
96
95 def close(self, data=None, metadata=None, buffers=None):
97 def close(self, data=None, metadata=None, buffers=None):
96 """Close the frontend-side version of this comm"""
98 """Close the frontend-side version of this comm"""
97 if self._closed:
99 if self._closed:
98 # only close once
100 # only close once
99 return
101 return
102 self._closed = True
100 if data is None:
103 if data is None:
101 data = self._close_data
104 data = self._close_data
102 self._publish_msg('comm_close',
105 self._publish_msg('comm_close',
103 data=data, metadata=metadata, buffers=buffers,
106 data=data, metadata=metadata, buffers=buffers,
104 )
107 )
105 self.kernel.comm_manager.unregister_comm(self)
108 self.kernel.comm_manager.unregister_comm(self)
106 self._closed = True
107
109
108 def send(self, data=None, metadata=None, buffers=None):
110 def send(self, data=None, metadata=None, buffers=None):
109 """Send a message to the frontend-side version of this comm"""
111 """Send a message to the frontend-side version of this comm"""
110 self._publish_msg('comm_msg',
112 self._publish_msg('comm_msg',
111 data=data, metadata=metadata, buffers=buffers,
113 data=data, metadata=metadata, buffers=buffers,
112 )
114 )
113
115
114 # registering callbacks
116 # registering callbacks
115
117
116 def on_close(self, callback):
118 def on_close(self, callback):
117 """Register a callback for comm_close
119 """Register a callback for comm_close
118
120
119 Will be called with the `data` of the close message.
121 Will be called with the `data` of the close message.
120
122
121 Call `on_close(None)` to disable an existing callback.
123 Call `on_close(None)` to disable an existing callback.
122 """
124 """
123 self._close_callback = callback
125 self._close_callback = callback
124
126
125 def on_msg(self, callback):
127 def on_msg(self, callback):
126 """Register a callback for comm_msg
128 """Register a callback for comm_msg
127
129
128 Will be called with the `data` of any comm_msg messages.
130 Will be called with the `data` of any comm_msg messages.
129
131
130 Call `on_msg(None)` to disable an existing callback.
132 Call `on_msg(None)` to disable an existing callback.
131 """
133 """
132 self._msg_callback = callback
134 self._msg_callback = callback
133
135
134 # handling of incoming messages
136 # handling of incoming messages
135
137
136 def handle_close(self, msg):
138 def handle_close(self, msg):
137 """Handle a comm_close message"""
139 """Handle a comm_close message"""
138 self.log.debug("handle_close[%s](%s)", self.comm_id, msg)
140 self.log.debug("handle_close[%s](%s)", self.comm_id, msg)
139 if self._close_callback:
141 if self._close_callback:
140 self._close_callback(msg)
142 self._close_callback(msg)
141
143
142 def handle_msg(self, msg):
144 def handle_msg(self, msg):
143 """Handle a comm_msg message"""
145 """Handle a comm_msg message"""
144 self.log.debug("handle_msg[%s](%s)", self.comm_id, msg)
146 self.log.debug("handle_msg[%s](%s)", self.comm_id, msg)
145 if self._msg_callback:
147 if self._msg_callback:
146 if self.shell:
148 if self.shell:
147 self.shell.events.trigger('pre_execute')
149 self.shell.events.trigger('pre_execute')
148 self._msg_callback(msg)
150 self._msg_callback(msg)
149 if self.shell:
151 if self.shell:
150 self.shell.events.trigger('post_execute')
152 self.shell.events.trigger('post_execute')
151
153
152
154
153 __all__ = ['Comm']
155 __all__ = ['Comm']
General Comments 0
You need to be logged in to leave comments. Login now