Skip to content

Commit 3399ee2

Browse files
committed
Added a state to track display_ids, added tests
1 parent d91a564 commit 3399ee2

File tree

2 files changed

+82
-46
lines changed

2 files changed

+82
-46
lines changed

jupyter_server_documents/outputs/manager.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313

1414
class OutputsManager(LoggingConfigurable):
1515
_last_output_index = Dict(default_value={})
16+
_display_id_output_index = Dict(default_value={})
17+
_display_ids = Dict(default_value={})
1618
_stream_count = Dict(default_value={})
17-
_output_index_by_display_id = Dict(default_value={})
1819

1920
outputs_path = Instance(PurePath, help="The local runtime dir")
2021
stream_limit = Int(default_value=10, config=True, allow_none=True)
@@ -35,24 +36,28 @@ def _build_path(self, file_id, cell_id=None, output_index=None):
3536
path = path / f"{output_index}.output"
3637
return path
3738

38-
def _determine_output_index(self, cell_id, display_id=None):
39+
def _compute_output_index(self, cell_id, display_id=None):
3940
"""
40-
Determines output index for a given cell and optional display ID.
41+
Computes next output index for a cell.
4142
4243
Args:
4344
cell_id (str): The cell identifier
4445
display_id (str, optional): A display identifier. Defaults to None.
4546
4647
Returns:
47-
int: The allocated output index
48+
int: The output index
4849
"""
4950
last_index = self._last_output_index.get(cell_id, -1)
5051
if display_id:
51-
index = self._output_index_by_display_id.get(display_id)
52+
if cell_id not in self._display_ids:
53+
self._display_ids[cell_id] = set([display_id])
54+
else:
55+
self._display_ids[cell_id].add(display_id)
56+
index = self._display_id_output_index.get(display_id)
5257
if index is None:
5358
index = last_index + 1
5459
self._last_output_index[cell_id] = index
55-
self._output_index_by_display_id[display_id] = index
60+
self._display_id_output_index[display_id] = index
5661
else:
5762
index = last_index + 1
5863
self._last_output_index[cell_id] = index
@@ -61,7 +66,7 @@ def _determine_output_index(self, cell_id, display_id=None):
6166

6267
def get_output_index(self, display_id: str):
6368
"""Returns output index for a cell by display_id"""
64-
return self._output_index_by_display_id.get(display_id)
69+
return self._display_id_output_index.get(display_id)
6570

6671
def get_output(self, file_id, cell_id, output_index):
6772
"""Get an output by file_id, cell_id, and output_index."""
@@ -120,7 +125,7 @@ def write(self, file_id, cell_id, output, display_id=None):
120125

121126
def write_output(self, file_id, cell_id, output, display_id=None):
122127
self._ensure_path(file_id, cell_id)
123-
index = self._determine_output_index(cell_id, display_id)
128+
index = self._compute_output_index(cell_id, display_id)
124129
path = self._build_path(file_id, cell_id, index)
125130
data = json.dumps(output, ensure_ascii=False)
126131
with open(path, "w", encoding="utf-8") as f:
@@ -161,17 +166,15 @@ def clear(self, file_id, cell_id=None):
161166
"""Clear the state of the manager."""
162167
if cell_id is None:
163168
self._stream_count = {}
164-
path = self._build_path(file_id)
165169
else:
166-
try:
167-
del self._stream_count[cell_id]
168-
except KeyError:
169-
pass
170-
try:
171-
del self._last_output_index[cell_id]
172-
except KeyError:
173-
pass
174-
path = self._build_path(file_id, cell_id)
170+
self._stream_count.pop(cell_id, None)
171+
self._last_output_index.pop(cell_id, None)
172+
173+
display_ids = self._display_ids.get(cell_id, [])
174+
for display_id in display_ids:
175+
self._display_id_output_index.pop(display_id, None)
176+
177+
path = self._build_path(file_id, cell_id)
175178
try:
176179
shutil.rmtree(path)
177180
except FileNotFoundError:

jupyter_server_documents/tests/test_outputs_manager.py

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -94,39 +94,72 @@ def file_not_found():
9494
op.get_stream('a','b')
9595

9696

97-
@pytest.mark.parametrize(
98-
"_last_output_index, _output_index_by_display_id, cell_id, display_id, expected", [
99-
# Empty dictionaries, no display_id
100-
({}, {}, "cell1", None, 0),
97+
def test__compute_output_index_basic():
98+
"""
99+
Test basic output index allocation for a cell without display ID
100+
"""
101+
op = OutputsManager()
101102

102-
# Empty dictionaries, with display_id
103-
({}, {}, "cell1", "display1", 0),
103+
# First output for a cell should be 0
104+
assert op._compute_output_index('cell1') == 0
105+
assert op._compute_output_index('cell1') == 1
106+
assert op._compute_output_index('cell1') == 2
107+
108+
def test__compute_output_index_with_display_id():
109+
"""
110+
Test output index allocation with display IDs
111+
"""
112+
op = OutputsManager()
104113

105-
# Existing last_output_index, no display_id
106-
({"cell1": 5}, {}, "cell1", None, 6),
107-
({"cell2": 3}, {}, "cell1", None, 0),
114+
# First output for a cell with display ID
115+
assert op._compute_output_index('cell1', 'display1') == 0
108116

109-
# Existing output_index_by_display_id
110-
({}, {"display1": 2}, "cell1", "display1", 2),
117+
# Subsequent calls with same display ID should return the same index
118+
assert op._compute_output_index('cell1', 'display1') == 0
111119

112-
# Existing last_output_index and display_id
113-
({"cell1": 5}, {"display1": 3}, "cell1", "display1", 3),
114-
({"cell1": 5}, {"display1": 3}, "cell1", "display2", 6),
120+
# Different display ID should get a new index
121+
assert op._compute_output_index('cell1', 'display2') == 1
122+
123+
124+
def test__compute_output_index_multiple_cells():
125+
"""
126+
Test output index allocation across multiple cells
127+
"""
128+
op = OutputsManager()
115129

116-
# Multiple cells with different indices
117-
({"cell1": 2, "cell2": 7}, {}, "cell1", None, 3),
118-
({"cell1": 2, "cell2": 7}, {}, "cell3", None, 0),
119-
])
120-
def test__determine_output_index(
121-
_last_output_index,
122-
_output_index_by_display_id,
123-
cell_id,
124-
display_id,
125-
expected
126-
):
130+
assert op._compute_output_index('cell1') == 0
131+
assert op._compute_output_index('cell1') == 1
132+
assert op._compute_output_index('cell2') == 0
133+
assert op._compute_output_index('cell2') == 1
134+
135+
def test_display_id_index_retrieval():
136+
"""
137+
Test retrieving output index for a display ID
138+
"""
127139
op = OutputsManager()
128-
op._last_output_index = _last_output_index;
129-
op._output_index_by_display_id = _output_index_by_display_id
130140

131-
assert expected == op._determine_output_index(cell_id, display_id)
141+
op._compute_output_index('cell1', 'display1')
142+
143+
assert op.get_output_index('display1') == 0
144+
assert op.get_output_index('non_existent_display') is None
132145

146+
def test_display_ids():
147+
"""
148+
Test tracking of display IDs for a cell
149+
"""
150+
op = OutputsManager()
151+
152+
# Allocate multiple display IDs for a cell
153+
op._compute_output_index('cell1', 'display1')
154+
op._compute_output_index('cell1', 'display2')
155+
156+
# Verify display IDs are tracked
157+
assert 'cell1' in op._display_ids
158+
assert set(op._display_ids['cell1']) == {'display1', 'display2'}
159+
160+
# Clear cell indices
161+
op.clear('file1', 'cell1')
162+
163+
# Verify display IDs are cleared
164+
assert 'display1' not in op._display_ids
165+
assert 'display2' not in op._display_ids

0 commit comments

Comments
 (0)