Skip to content

Commit b401cb4

Browse files
Handle Yjs awareness on the widget comm (#40)
* Support awareness over Comm * Tests * Lint * Coverage * Apply suggestions and update test * Remove test * Apply suggestions * Lint * Me good at rebase >_> --------- Co-authored-by: David Brochart <david.brochart@gmail.com>
1 parent 4a2a508 commit b401cb4

2 files changed

Lines changed: 60 additions & 1 deletion

File tree

src/ypywidgets/comm.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import comm
44
from pycrdt import (
5+
Awareness,
56
Doc,
67
Text,
78
TransactionEvent,
@@ -10,6 +11,7 @@
1011
create_sync_message,
1112
create_update_message,
1213
handle_sync_message,
14+
read_message,
1315
)
1416

1517
from .widget import Widget
@@ -48,10 +50,15 @@ def __init__(
4850
) -> None:
4951
self._ydoc = ydoc
5052
self._comm = comm
53+
self._awareness = Awareness(ydoc)
5154
msg = create_sync_message(ydoc)
5255
self._comm.send(buffers=[msg])
5356
self._comm.on_msg(self._receive)
5457

58+
@property
59+
def awareness(self) -> Awareness:
60+
return self._awareness
61+
5562
def _receive(self, msg):
5663
message = bytes(msg["buffers"][0])
5764
match message[0]:
@@ -61,6 +68,10 @@ def _receive(self, msg):
6168
self._comm.send(buffers=[reply])
6269
if message[1] == YSyncMessageType.SYNC_STEP2:
6370
self._ydoc.observe(self._send)
71+
case YMessageType.AWARENESS:
72+
# Same as pycrdt.websocket.yroom: strip Y message kind, decode body.
73+
update = read_message(message[1:])
74+
self._awareness.apply_awareness_update(update, None)
6475

6576
def _send(self, event: TransactionEvent):
6677
update = event.update
@@ -86,7 +97,11 @@ def __init__(
8697
create_ydoc=not ydoc,
8798
)
8899
self._comm = create_widget_comm(comm_data, comm_metadata, comm_id)
89-
CommProvider(self.ydoc, self._comm)
100+
self._comm_provider = CommProvider(self.ydoc, self._comm)
101+
102+
@property
103+
def awareness(self) -> Awareness:
104+
return self._comm_provider.awareness
90105

91106
def _repr_mimebundle_(self, *args, **kwargs): # pragma: nocover
92107
plaintext = repr(self)

tests/test_comm_awareness.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
from pycrdt import Awareness, Doc, YMessageType, create_awareness_message
5+
from ypywidgets.comm import CommWidget
6+
7+
pytestmark = pytest.mark.anyio
8+
9+
10+
async def test_comm_provider_applies_awareness_message(synced_widgets, context):
11+
async with context:
12+
local_widget = await synced_widgets.get_local_widget()
13+
remote_awareness = Awareness(Doc())
14+
remote_awareness.set_local_state({"role": "remote"})
15+
payload = remote_awareness.encode_awareness_update([remote_awareness.client_id])
16+
message = create_awareness_message(payload)
17+
18+
assert message[0] == YMessageType.AWARENESS
19+
20+
local_widget._comm_provider._receive({"buffers": [message]})
21+
22+
remote_state = local_widget.awareness.states.get(remote_awareness.client_id)
23+
assert remote_state is not None
24+
assert remote_state.get("role") == "remote"
25+
26+
27+
async def test_comm_widget_exposes_provider_awareness():
28+
widget = CommWidget()
29+
assert widget.awareness is widget._comm_provider.awareness
30+
31+
32+
async def test_comm_widget_awareness_observe_and_unobserve():
33+
widget = CommWidget()
34+
35+
events: list[str] = []
36+
sub_id = widget.awareness.observe(lambda topic, _: events.append(topic))
37+
38+
widget.awareness.set_local_state({"ping": 1})
39+
assert events
40+
41+
widget.awareness.unobserve(sub_id)
42+
events.clear()
43+
widget.awareness.set_local_state({"ping": 2})
44+
assert events == []

0 commit comments

Comments
 (0)