Skip to content

Commit 55d21e0

Browse files
authored
Make use of the new Comm package (#54)
* Make use of the Comm module This goes in the direction of removing the ipykernel shim in the (how long?) long run. * Linting * Missing import * More linting and missing imports * Fix comm_open/msg/close calls * Forgot to run prettier? * Let's see * Using my comms
1 parent b545bf3 commit 55d21e0

File tree

6 files changed

+51
-229
lines changed

6 files changed

+51
-229
lines changed
Lines changed: 4 additions & 223 deletions
Original file line numberDiff line numberDiff line change
@@ -1,225 +1,6 @@
1-
# This is more or less a copy of ipykernel's comm implementation
1+
from comm import get_comm_manager
22

3-
import uuid
3+
from pyodide_kernel.comm import Comm # noqa
44

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
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import comm
2+
from comm import BaseComm, get_comm_manager, CommManager # noqa
3+
4+
from IPython.core.getipython import get_ipython
5+
6+
7+
class Comm(BaseComm):
8+
def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys):
9+
"""Helper for sending a comm message on IOPub"""
10+
data = {} if data is None else data
11+
metadata = {} if metadata is None else metadata
12+
content = dict(data=data, comm_id=self.comm_id, **keys)
13+
14+
if buffers is not None:
15+
buffers = [(b.tobytes() if hasattr(b, "tobytes") else b) for b in buffers]
16+
17+
get_ipython().send_comm(
18+
msg_type,
19+
content,
20+
metadata,
21+
self.topic,
22+
buffers,
23+
)
24+
25+
26+
comm.create_comm = Comm

packages/pyodide-kernel/py/pyodide-kernel/pyodide_kernel/display.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import sys
22

3-
from ipykernel.jsonutil import encode_images, json_clean
43
from IPython.core.displayhook import DisplayHook
54
from IPython.core.displaypub import DisplayPublisher
65
from IPython.display import Image # to replace previous base64-encoding shim
76

7+
from .jsonutil import encode_images, json_clean
8+
89
__all__ = ["LiteStream", "Image", "LiteDisplayHook", "LiteDisplayPublisher"]
910

1011

packages/pyodide-kernel/py/pyodide-kernel/pyodide_kernel/kernel.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# This is our ipykernel mock
22
import typing
33

4-
from ipykernel import CommManager
4+
from .comm import get_comm_manager, CommManager
5+
56
from IPython.utils.tokenutil import line_at_cursor, token_at_cursor
67
from pyodide_js import loadPackagesFromImports as _load_packages_from_imports
78
from traitlets import Any, Instance, default
@@ -23,7 +24,7 @@ class PyodideKernel(LoggingConfigurable):
2324

2425
@default("comm_manager")
2526
def _default_comm_manager(self):
26-
return CommManager(kernel=self)
27+
return get_comm_manager()
2728

2829
def get_parent(self):
2930
# TODO mimic ipykernel's get_parent signature

packages/pyodide-kernel/src/worker.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export class PyodideRemoteKernel {
7979
await this._pyodide.runPythonAsync(`
8080
await piplite.install(['sqlite3'], keep_going=True);
8181
await piplite.install(['ipykernel'], keep_going=True);
82+
await piplite.install(['comm'], keep_going=True);
8283
await piplite.install(['pyodide_kernel'], keep_going=True);
8384
await piplite.install(['ipython'], keep_going=True);
8485
import pyodide_kernel
@@ -348,7 +349,11 @@ export class PyodideRemoteKernel {
348349
async commOpen(content: any, parent: any) {
349350
await this.setup(parent);
350351

351-
const res = this._kernel.comm_manager.comm_open(this._pyodide.toPy(content));
352+
const res = this._kernel.comm_manager.comm_open(
353+
this._pyodide.toPy(null),
354+
this._pyodide.toPy(null),
355+
this._pyodide.toPy(content)
356+
);
352357
const results = this.formatResult(res);
353358

354359
return results;
@@ -362,7 +367,11 @@ export class PyodideRemoteKernel {
362367
async commMsg(content: any, parent: any) {
363368
await this.setup(parent);
364369

365-
const res = this._kernel.comm_manager.comm_msg(this._pyodide.toPy(content));
370+
const res = this._kernel.comm_manager.comm_msg(
371+
this._pyodide.toPy(null),
372+
this._pyodide.toPy(null),
373+
this._pyodide.toPy(content)
374+
);
366375
const results = this.formatResult(res);
367376

368377
return results;
@@ -376,7 +385,11 @@ export class PyodideRemoteKernel {
376385
async commClose(content: any, parent: any) {
377386
await this.setup(parent);
378387

379-
const res = this._kernel.comm_manager.comm_close(this._pyodide.toPy(content));
388+
const res = this._kernel.comm_manager.comm_close(
389+
this._pyodide.toPy(null),
390+
this._pyodide.toPy(null),
391+
this._pyodide.toPy(content)
392+
);
380393
const results = this.formatResult(res);
381394

382395
return results;

0 commit comments

Comments
 (0)