##// END OF EJS Templates
deleting a comm now unregisters the comm. unrehistering the comm does not close it anymore
sylvain.corlay -
Show More
@@ -1,133 +1,134 b''
1 1 """Base class for a Comm"""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import uuid
7 7
8 8 from IPython.config import LoggingConfigurable
9 9 from IPython.core.getipython import get_ipython
10 10
11 11 from IPython.utils.jsonutil import json_clean
12 12 from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any
13 13
14 14
15 15 class Comm(LoggingConfigurable):
16 16
17 17 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
18 18 def _shell_default(self):
19 19 return get_ipython()
20 20
21 21 iopub_socket = Any()
22 22 def _iopub_socket_default(self):
23 23 return self.shell.kernel.iopub_socket
24 24 session = Instance('IPython.kernel.zmq.session.Session')
25 25 def _session_default(self):
26 26 if self.shell is None:
27 27 return
28 28 return self.shell.kernel.session
29 29
30 30 target_name = Unicode('comm')
31 31
32 32 topic = Bytes()
33 33 def _topic_default(self):
34 34 return ('comm-%s' % self.comm_id).encode('ascii')
35 35
36 36 _open_data = Dict(help="data dict, if any, to be included in comm_open")
37 37 _close_data = Dict(help="data dict, if any, to be included in comm_close")
38 38
39 39 _msg_callback = Any()
40 40 _close_callback = Any()
41 41
42 42 _closed = Bool(False)
43 43 comm_id = Unicode()
44 44 def _comm_id_default(self):
45 45 return uuid.uuid4().hex
46 46
47 47 primary = Bool(True, help="Am I the primary or secondary Comm?")
48 48
49 49 def __init__(self, target_name='', data=None, **kwargs):
50 50 if target_name:
51 51 kwargs['target_name'] = target_name
52 52 super(Comm, self).__init__(**kwargs)
53 53 get_ipython().comm_manager.register_comm(self)
54 54 if self.primary:
55 55 # I am primary, open my peer.
56 56 self.open(data)
57 57
58 58 def _publish_msg(self, msg_type, data=None, metadata=None, **keys):
59 59 """Helper for sending a comm message on IOPub"""
60 60 data = {} if data is None else data
61 61 metadata = {} if metadata is None else metadata
62 62 content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))
63 63 self.session.send(self.iopub_socket, msg_type,
64 64 content,
65 65 metadata=json_clean(metadata),
66 66 parent=self.shell.get_parent(),
67 67 ident=self.topic,
68 68 )
69 69
70 70 def __del__(self):
71 71 """trigger close on gc"""
72 72 self.close()
73 get_ipython().comm_manager.unregister_comm(self)
73 74
74 75 # publishing messages
75 76
76 77 def open(self, data=None, metadata=None):
77 78 """Open the frontend-side version of this comm"""
78 79 if data is None:
79 80 data = self._open_data
80 81 self._publish_msg('comm_open', data, metadata, target_name=self.target_name)
81 82
82 83 def close(self, data=None, metadata=None):
83 84 """Close the frontend-side version of this comm"""
84 85 if self._closed:
85 86 # only close once
86 87 return
87 88 if data is None:
88 89 data = self._close_data
89 90 self._publish_msg('comm_close', data, metadata)
90 91 self._closed = True
91 92
92 93 def send(self, data=None, metadata=None):
93 94 """Send a message to the frontend-side version of this comm"""
94 95 self._publish_msg('comm_msg', data, metadata)
95 96
96 97 # registering callbacks
97 98
98 99 def on_close(self, callback):
99 100 """Register a callback for comm_close
100 101
101 102 Will be called with the `data` of the close message.
102 103
103 104 Call `on_close(None)` to disable an existing callback.
104 105 """
105 106 self._close_callback = callback
106 107
107 108 def on_msg(self, callback):
108 109 """Register a callback for comm_msg
109 110
110 111 Will be called with the `data` of any comm_msg messages.
111 112
112 113 Call `on_msg(None)` to disable an existing callback.
113 114 """
114 115 self._msg_callback = callback
115 116
116 117 # handling of incoming messages
117 118
118 119 def handle_close(self, msg):
119 120 """Handle a comm_close message"""
120 121 self.log.debug("handle_close[%s](%s)", self.comm_id, msg)
121 122 if self._close_callback:
122 123 self._close_callback(msg)
123 124
124 125 def handle_msg(self, msg):
125 126 """Handle a comm_msg message"""
126 127 self.log.debug("handle_msg[%s](%s)", self.comm_id, msg)
127 128 if self._msg_callback:
128 129 self.shell.events.trigger('pre_execute')
129 130 self._msg_callback(msg)
130 131 self.shell.events.trigger('post_execute')
131 132
132 133
133 134 __all__ = ['Comm']
@@ -1,151 +1,150 b''
1 1 """Base class to manage comms"""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import sys
7 7
8 8 from IPython.config import LoggingConfigurable
9 9 from IPython.core.prompts import LazyEvaluate
10 10 from IPython.core.getipython import get_ipython
11 11
12 12 from IPython.utils.importstring import import_item
13 13 from IPython.utils.py3compat import string_types
14 14 from IPython.utils.traitlets import Instance, Unicode, Dict, Any
15 15
16 16 from .comm import Comm
17 17
18 18
19 19 def lazy_keys(dikt):
20 20 """Return lazy-evaluated string representation of a dictionary's keys
21 21
22 22 Key list is only constructed if it will actually be used.
23 23 Used for debug-logging.
24 24 """
25 25 return LazyEvaluate(lambda d: list(d.keys()))
26 26
27 27
28 28 class CommManager(LoggingConfigurable):
29 29 """Manager for Comms in the Kernel"""
30 30
31 31 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
32 32 def _shell_default(self):
33 33 return get_ipython()
34 34 iopub_socket = Any()
35 35 def _iopub_socket_default(self):
36 36 return self.shell.kernel.iopub_socket
37 37 session = Instance('IPython.kernel.zmq.session.Session')
38 38 def _session_default(self):
39 39 if self.shell is None:
40 40 return
41 41 return self.shell.kernel.session
42 42
43 43 comms = Dict()
44 44 targets = Dict()
45 45
46 46 # Public APIs
47 47
48 48 def register_target(self, target_name, f):
49 49 """Register a callable f for a given target name
50 50
51 51 f will be called with two arguments when a comm_open message is received with `target`:
52 52
53 53 - the Comm instance
54 54 - the `comm_open` message itself.
55 55
56 56 f can be a Python callable or an import string for one.
57 57 """
58 58 if isinstance(f, string_types):
59 59 f = import_item(f)
60 60
61 61 self.targets[target_name] = f
62 62
63 63 def unregister_target(self, target_name, f):
64 64 """Unregister a callable registered with register_target"""
65 65 return self.targets.pop(target_name);
66 66
67 67 def register_comm(self, comm):
68 68 """Register a new comm"""
69 69 comm_id = comm.comm_id
70 70 comm.shell = self.shell
71 71 comm.iopub_socket = self.iopub_socket
72 72 self.comms[comm_id] = comm
73 73 return comm_id
74 74
75 75 def unregister_comm(self, comm_id):
76 76 """Unregister a comm, and close its counterpart"""
77 77 # unlike get_comm, this should raise a KeyError
78 78 comm = self.comms.pop(comm_id)
79 comm.close()
80 79
81 80 def get_comm(self, comm_id):
82 81 """Get a comm with a particular id
83 82
84 83 Returns the comm if found, otherwise None.
85 84
86 85 This will not raise an error,
87 86 it will log messages if the comm cannot be found.
88 87 """
89 88 if comm_id not in self.comms:
90 89 self.log.error("No such comm: %s", comm_id)
91 90 self.log.debug("Current comms: %s", lazy_keys(self.comms))
92 91 return
93 92 # call, because we store weakrefs
94 93 comm = self.comms[comm_id]
95 94 return comm
96 95
97 96 # Message handlers
98 97 def comm_open(self, stream, ident, msg):
99 98 """Handler for comm_open messages"""
100 99 content = msg['content']
101 100 comm_id = content['comm_id']
102 101 target_name = content['target_name']
103 102 f = self.targets.get(target_name, None)
104 103 comm = Comm(comm_id=comm_id,
105 104 shell=self.shell,
106 105 iopub_socket=self.iopub_socket,
107 106 primary=False,
108 107 )
109 108 if f is None:
110 109 self.log.error("No such comm target registered: %s", target_name)
111 110 comm.close()
112 111 return
113 112 self.register_comm(comm)
114 113 try:
115 114 f(comm, msg)
116 115 except Exception:
117 116 self.log.error("Exception opening comm with target: %s", target_name, exc_info=True)
118 117 comm.close()
119 118 self.unregister_comm(comm_id)
120 119
121 120 def comm_msg(self, stream, ident, msg):
122 121 """Handler for comm_msg messages"""
123 122 content = msg['content']
124 123 comm_id = content['comm_id']
125 124 comm = self.get_comm(comm_id)
126 125 if comm is None:
127 126 # no such comm
128 127 return
129 128 try:
130 129 comm.handle_msg(msg)
131 130 except Exception:
132 131 self.log.error("Exception in comm_msg for %s", comm_id, exc_info=True)
133 132
134 133 def comm_close(self, stream, ident, msg):
135 134 """Handler for comm_close messages"""
136 135 content = msg['content']
137 136 comm_id = content['comm_id']
138 137 comm = self.get_comm(comm_id)
139 138 if comm is None:
140 139 # no such comm
141 140 self.log.debug("No such comm to close: %s", comm_id)
142 141 return
143 142 del self.comms[comm_id]
144 143
145 144 try:
146 145 comm.handle_close(msg)
147 146 except Exception:
148 147 self.log.error("Exception handling comm_close for %s", comm_id, exc_info=True)
149 148
150 149
151 150 __all__ = ['CommManager']
General Comments 0
You need to be logged in to leave comments. Login now