Source code for transports.comm

"""Serve a `Session`/`Hub` over a Jupyter comm channel.

A Jupyter **comm** is a bidirectional message channel between a kernel and a frontend (the transport
behind ipywidgets). It carries JSON-able data natively, so transports rides it by sending the wire
*string* as the comm's ``data`` — no extra framing, and the same `Client.recv`/`edit` work unchanged.

This adapter never imports `comm`/`ipykernel`: you pass in a comm (created by your widget, or
`comm.create_comm(...)`), keeping the Jupyter dependency optional. It is therefore also testable with
a duck-typed fake comm exposing ``send`` / ``on_msg`` / ``on_close``.

```python
import transports

session = transports.Session()
session.host(model)
server = transports.Server(session)

transports.serve_comm(server, my_comm)   # sends snapshots, wires inbound messages
# ... mutate model(s) ...
transports.sync(server)                  # push host-side changes to every connection
```
"""

from typing import Any

from . import protocol
from .server import Broadcaster


class _CommConn:
    """A Jupyter comm as a transports connection handle: a wire is sent as the comm's ``data``.

    Wrapping the comm gives every connection a uniform ``send(wire)``, so `sync`/`autosync` deliver to
    comms, anywidgets, and sockets the same way."""

    __slots__ = ("comm",)

    def __init__(self, comm: Any) -> None:
        self.comm = comm

    def send(self, wire: Any) -> None:
        self.comm.send(data=wire)


[docs] def serve_comm(server: Broadcaster, comm: Any, codec: str = protocol.JSON) -> _CommConn: """Wire a comm to a `Server`/`Hub`: send the opening snapshots and relay inbound messages. The comm is registered as a connection; its messages (``msg["content"]["data"]``) are fed to ``server.recv`` and any resulting messages are sent back over the relevant connections. Returns the connection handle. Call `sync(server)` after host-side mutations to push changes. """ if protocol.normalize_codec(codec) != protocol.JSON: raise ValueError(f"the comm transport carries JSON-able data only; codec {codec!r} is not supported") conn = _CommConn(comm) for wire in server.open(conn, codec): conn.send(wire) def _on_msg(msg: dict) -> None: for target, msgs in server.recv(conn, msg["content"]["data"]).items(): for m in msgs: target.send(m) comm.on_msg(_on_msg) comm.on_close(lambda _msg: server.close(conn)) return conn