Skip to content

Commit 0a0d0bc

Browse files
authored
Hard-reload a cell if its outputs change (#362)
* Add a test for outputs being hard-reset * Fix typo in `outputs` key * Add a test for allowing reload for non-stream outputs * Allow reload of outputs array if none of the outputs is a stream
1 parent 87919c6 commit 0a0d0bc

File tree

2 files changed

+60
-11
lines changed

2 files changed

+60
-11
lines changed

jupyter_ydoc/ynotebook.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,14 @@ def _update_cell(self, old_cell: dict, new_cell: dict, old_ycell: Map) -> bool:
345345
for key in shared_keys:
346346
if old_cell[key] != new_cell[key]:
347347
value = new_cell[key]
348-
if key == "output" and value:
349-
# outputs require complex handling - some have Text type nested;
350-
# for now skip creating them; clearing all outputs is fine
348+
if (
349+
key == "outputs"
350+
and value
351+
and any(output.get("output_type") == "stream" for output in value)
352+
):
353+
# Outputs with stream require complex handling as they have
354+
# the Text type nested inside; for now skip creating them.
355+
# Clearing all outputs is fine.
351356
return False
352357

353358
if key in _CELL_KEY_TYPE_MAP:

tests/test_ynotebook.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright (c) Jupyter Development Team.
22
# Distributed under the terms of the Modified BSD License.
33

4+
from dataclasses import dataclass
45

56
from pycrdt import ArrayEvent, Map, MapEvent, TextEvent
67
from pytest import mark
@@ -118,17 +119,61 @@ def record_changes(topic, event):
118119
]
119120

120121

122+
@dataclass
123+
class ExpectedEvent:
124+
kind: type
125+
path: str | None = None
126+
127+
def __eq__(self, other):
128+
if not isinstance(other, self.kind):
129+
return False
130+
if self.path is not None and self.path != other.path:
131+
return False
132+
return True
133+
134+
def __repr__(self):
135+
if self.path is not None:
136+
return f"ExpectedEvent({self.kind.__name__}, path={self.path!r})"
137+
return f"ExpectedEvent({self.kind.__name__})"
138+
139+
121140
@mark.parametrize(
122141
"modifications, expected_events",
123142
[
124143
# modifications of single attributes
125-
([["source", "'b'"]], {TextEvent}),
126-
([["outputs", []]], {ArrayEvent}),
127-
([["execution_count", 2]], {MapEvent}),
128-
([["metadata", {"tags": []}]], {MapEvent}),
129-
([["new_key", "test"]], {MapEvent}),
144+
([["source", "'b'"]], [ExpectedEvent(TextEvent)]),
145+
([["execution_count", 2]], [ExpectedEvent(MapEvent)]),
146+
([["metadata", {"tags": []}]], [ExpectedEvent(MapEvent)]),
147+
([["new_key", "test"]], [ExpectedEvent(MapEvent)]),
148+
# outputs can be cleared using granular logic
149+
([["outputs", []]], [ExpectedEvent(ArrayEvent, path=[0, "outputs"])]),
150+
# stream outputs require a hard cell reload, which is why we expect top-level array change
151+
(
152+
[["outputs", [{"name": "stdout", "output_type": "stream", "text": "b\n"}]]],
153+
[ExpectedEvent(ArrayEvent, path=[])],
154+
),
155+
# other output types can be changed granularly
156+
(
157+
[
158+
[
159+
"outputs",
160+
[
161+
{
162+
"data": {"text/plain": ["1"]},
163+
"execution_count": 1,
164+
"metadata": {},
165+
"output_type": "execute_result",
166+
}
167+
],
168+
]
169+
],
170+
[ExpectedEvent(ArrayEvent, path=[0, "outputs"])],
171+
),
130172
# multi-attribute modifications
131-
([["source", "10"], ["execution_count", 10]], {TextEvent, MapEvent}),
173+
(
174+
[["source", "10"], ["execution_count", 10]],
175+
[ExpectedEvent(MapEvent), ExpectedEvent(TextEvent)],
176+
),
132177
],
133178
)
134179
def test_modify_single_cell(modifications, expected_events):
@@ -177,5 +222,4 @@ def record_changes(topic, event):
177222
assert len(cell_events) == 1
178223
# but it should be a change to cell data, not a change to the cell list
179224
events = cell_events[0]
180-
assert len(events) == len(expected_events)
181-
assert {type(e) for e in events} == expected_events
225+
assert events == expected_events

0 commit comments

Comments
 (0)