|
1 | | -# This is more or less a copy of ipykernel's comm implementation |
| 1 | +from comm import get_comm_manager |
2 | 2 |
|
3 | | -import uuid |
| 3 | +from pyodide_kernel.comm import Comm # noqa |
4 | 4 |
|
5 | | -from traitlets.utils.importstring import import_item |
6 | | - |
7 | | - |
8 | | -class Comm: |
9 | | - def __init__( |
10 | | - self, |
11 | | - target_name="", |
12 | | - data=None, |
13 | | - metadata=None, |
14 | | - buffers=None, |
15 | | - primary=True, |
16 | | - target_module=None, |
17 | | - comm_id=None, |
18 | | - **kwargs, |
19 | | - ): |
20 | | - from IPython.core.getipython import get_ipython |
21 | | - |
22 | | - self.target_name = target_name |
23 | | - self.target_module = target_module |
24 | | - self.comm_id = comm_id if comm_id is not None else uuid.uuid4().hex |
25 | | - self.topic = ("comm-%s" % self.comm_id).encode("ascii") |
26 | | - self.kernel = get_ipython().kernel |
27 | | - self._msg_callback = [] |
28 | | - self._close_callback = [] |
29 | | - self._closed = True |
30 | | - self.primary = primary |
31 | | - if self.kernel: |
32 | | - if self.primary: |
33 | | - # I am primary, open my peer. |
34 | | - self.open(data=data, metadata=metadata, buffers=buffers) |
35 | | - else: |
36 | | - self._closed = False |
37 | | - |
38 | | - def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): |
39 | | - """Helper for sending a comm message on IOPub""" |
40 | | - data = {} if data is None else data |
41 | | - metadata = {} if metadata is None else metadata |
42 | | - content = dict(data=data, comm_id=self.comm_id, **keys) |
43 | | - if buffers is not None: |
44 | | - buffers = [(b.tobytes() if hasattr(b, "tobytes") else b) for b in buffers] |
45 | | - |
46 | | - self.kernel.interpreter.send_comm( |
47 | | - msg_type, |
48 | | - content, |
49 | | - metadata, |
50 | | - self.topic, |
51 | | - buffers, |
52 | | - ) |
53 | | - |
54 | | - def __del__(self): |
55 | | - """trigger close on gc""" |
56 | | - self.close(deleting=True) |
57 | | - |
58 | | - # publishing messages |
59 | | - |
60 | | - def open(self, data=None, metadata=None, buffers=None): |
61 | | - """Open the frontend-side version of this comm""" |
62 | | - comm_manager = getattr(self.kernel, "comm_manager", None) |
63 | | - if comm_manager is None: |
64 | | - raise RuntimeError( |
65 | | - "Comms cannot be opened without a kernel " |
66 | | - "and a comm_manager attached to that kernel." |
67 | | - ) |
68 | | - |
69 | | - comm_manager.register_comm(self) |
70 | | - try: |
71 | | - self._publish_msg( |
72 | | - "comm_open", |
73 | | - data=data, |
74 | | - metadata=metadata, |
75 | | - buffers=buffers, |
76 | | - target_name=self.target_name, |
77 | | - target_module=self.target_module, |
78 | | - ) |
79 | | - self._closed = False |
80 | | - except: |
81 | | - comm_manager.unregister_comm(self) |
82 | | - raise |
83 | | - |
84 | | - def close(self, data=None, metadata=None, buffers=None, deleting=False): |
85 | | - """Close the frontend-side version of this comm""" |
86 | | - if self._closed: |
87 | | - # only close once |
88 | | - return |
89 | | - self._closed = True |
90 | | - # nothing to send if we have no kernel |
91 | | - # can be None during interpreter cleanup |
92 | | - if not self.kernel: |
93 | | - return |
94 | | - self._publish_msg( |
95 | | - "comm_close", |
96 | | - data=data, |
97 | | - metadata=metadata, |
98 | | - buffers=buffers, |
99 | | - ) |
100 | | - if not deleting: |
101 | | - # If deleting, the comm can't be registered |
102 | | - self.kernel.comm_manager.unregister_comm(self) |
103 | | - |
104 | | - def send(self, data=None, metadata=None, buffers=None): |
105 | | - """Send a message to the frontend-side version of this comm""" |
106 | | - self._publish_msg( |
107 | | - "comm_msg", |
108 | | - data=data, |
109 | | - metadata=metadata, |
110 | | - buffers=buffers, |
111 | | - ) |
112 | | - |
113 | | - # registering callbacks |
114 | | - |
115 | | - def on_close(self, callback): |
116 | | - """Register a callback for comm_close |
117 | | - Will be called with the `data` of the close message. |
118 | | - Call `on_close(None)` to disable an existing callback. |
119 | | - """ |
120 | | - self._close_callback = callback |
121 | | - |
122 | | - def on_msg(self, callback): |
123 | | - """Register a callback for comm_msg |
124 | | - Will be called with the `data` of any comm_msg messages. |
125 | | - Call `on_msg(None)` to disable an existing callback. |
126 | | - """ |
127 | | - self._msg_callback = callback |
128 | | - |
129 | | - # handling of incoming messages |
130 | | - |
131 | | - def handle_close(self, msg): |
132 | | - """Handle a comm_close message""" |
133 | | - if self._close_callback: |
134 | | - self._close_callback(msg) |
135 | | - |
136 | | - def handle_msg(self, msg): |
137 | | - """Handle a comm_msg message""" |
138 | | - if self._msg_callback: |
139 | | - self._msg_callback(msg) |
140 | | - |
141 | | - |
142 | | -class CommManager: |
143 | | - def __init__(self, kernel=None, comms=None, targets=None): |
144 | | - self.kernel = kernel |
145 | | - self.comms = comms if comms is not None else {} |
146 | | - self.targets = targets if targets is not None else {} |
147 | | - |
148 | | - def register_target(self, target_name, f): |
149 | | - """Register a callable ``f`` for a given target name |
150 | | - ``f`` will be called with two arguments when a comm_open message is |
151 | | - received with ``target``: |
152 | | - - the ``Comm`` instance |
153 | | - - the ``comm_open`` message itself. |
154 | | - ``f`` can be a Python callable or an import string for one. |
155 | | - """ |
156 | | - if isinstance(f, str): |
157 | | - f = import_item(f) |
158 | | - |
159 | | - self.targets[target_name] = f |
160 | | - |
161 | | - def unregister_target(self, target_name, f): |
162 | | - """Unregister a callable registered with register_target""" |
163 | | - return self.targets.pop(target_name) |
164 | | - |
165 | | - def register_comm(self, comm): |
166 | | - """Register a new comm""" |
167 | | - comm_id = comm.comm_id |
168 | | - comm.kernel = self.kernel |
169 | | - self.comms[comm_id] = comm |
170 | | - return comm_id |
171 | | - |
172 | | - def unregister_comm(self, comm): |
173 | | - """Unregister a comm, and close its counterpart""" |
174 | | - # unlike get_comm, this should raise a KeyError |
175 | | - comm = self.comms.pop(comm.comm_id) |
176 | | - |
177 | | - def get_comm(self, comm_id): |
178 | | - """Get a comm with a particular id |
179 | | - Returns the comm if found, otherwise None. |
180 | | - This will not raise an error, |
181 | | - it will log messages if the comm cannot be found. |
182 | | - """ |
183 | | - try: |
184 | | - return self.comms[comm_id] |
185 | | - except KeyError: |
186 | | - pass |
187 | | - |
188 | | - # Message handlers |
189 | | - def comm_open(self, msg): |
190 | | - """Handler for comm_open messages""" |
191 | | - content = msg["content"] |
192 | | - comm_id = content["comm_id"] |
193 | | - target_name = content["target_name"] |
194 | | - f = self.targets.get(target_name, None) |
195 | | - comm = Comm( |
196 | | - comm_id=comm_id, |
197 | | - primary=False, |
198 | | - target_name=target_name, |
199 | | - ) |
200 | | - self.register_comm(comm) |
201 | | - if f is not None: |
202 | | - f(comm, msg) |
203 | | - |
204 | | - def comm_msg(self, msg): |
205 | | - """Handler for comm_msg messages""" |
206 | | - content = msg["content"] |
207 | | - comm_id = content["comm_id"] |
208 | | - comm = self.get_comm(comm_id) |
209 | | - if comm is None: |
210 | | - return |
211 | | - |
212 | | - comm.handle_msg(msg) |
213 | | - |
214 | | - def comm_close(self, msg): |
215 | | - """Handler for comm_close messages""" |
216 | | - content = msg["content"] |
217 | | - comm_id = content["comm_id"] |
218 | | - comm = self.get_comm(comm_id) |
219 | | - if comm is None: |
220 | | - return |
221 | | - |
222 | | - self.comms[comm_id]._closed = True |
223 | | - del self.comms[comm_id] |
224 | | - |
225 | | - comm.handle_close(msg) |
| 5 | +# Backward compat, in case someone was relying on importing CommManager? |
| 6 | +CommManager = get_comm_manager # noqa |
0 commit comments