Skip to content

Commit 41a1ad1

Browse files
Merge pull request #35 from jupyter-server/get_set_cell
Add YNotebook get_cell, set_cell, append_cell
2 parents be74ae4 + 35c3466 commit 41a1ad1

File tree

3 files changed

+82
-56
lines changed

3 files changed

+82
-56
lines changed

jupyter_ydoc/ydoc.py

Lines changed: 76 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import copy
2+
from typing import Any, Dict
23
from uuid import uuid4
34

45
import y_py as Y
5-
from ypy_websocket.websocket_server import YDoc
66

77
from .utils import cast_all
88

99

1010
class YBaseDoc:
11-
def __init__(self, ydoc: YDoc):
11+
def __init__(self, ydoc: Y.YDoc):
1212
self._ydoc = ydoc
1313
self._ystate = self._ydoc.get_map("state")
1414
self._subscriptions = {}
@@ -23,11 +23,11 @@ def ydoc(self):
2323

2424
@property
2525
def source(self):
26-
raise RuntimeError("Y document source generation not implemented")
26+
return self.get()
2727

2828
@source.setter
2929
def source(self, value):
30-
raise RuntimeError("Y document source initialization not implemented")
30+
return self.set(value)
3131

3232
@property
3333
def dirty(self) -> None:
@@ -38,6 +38,12 @@ def dirty(self, value: bool) -> None:
3838
with self._ydoc.begin_transaction() as t:
3939
self._ystate.set(t, "dirty", value)
4040

41+
def get(self):
42+
raise RuntimeError("Y document get not implemented")
43+
44+
def set(self, value):
45+
raise RuntimeError("Y document set not implemented")
46+
4147
def observe(self, callback):
4248
raise RuntimeError("Y document observe not implemented")
4349

@@ -52,12 +58,10 @@ def __init__(self, *args, **kwargs):
5258
super().__init__(*args, **kwargs)
5359
self._ysource = self._ydoc.get_text("source")
5460

55-
@property
56-
def source(self):
61+
def get(self):
5762
return str(self._ysource)
5863

59-
@source.setter
60-
def source(self, value):
64+
def set(self, value):
6165
with self._ydoc.begin_transaction() as t:
6266
# clear document
6367
source_len = len(self._ysource)
@@ -79,18 +83,63 @@ def __init__(self, *args, **kwargs):
7983
self._ymeta = self._ydoc.get_map("meta")
8084
self._ycells = self._ydoc.get_array("cells")
8185

82-
@property
83-
def source(self):
86+
def get_cell(self, index: int) -> Dict[str, Any]:
87+
meta = self._ymeta.to_json()
88+
cell = self._ycells[index].to_json()
89+
cast_all(cell, float, int)
90+
if "id" in cell and meta["nbformat"] == 4 and meta["nbformat_minor"] <= 4:
91+
# strip cell IDs if we have notebook format 4.0-4.4
92+
del cell["id"]
93+
if cell["cell_type"] in ["raw", "markdown"] and not cell["attachments"]:
94+
del cell["attachments"]
95+
return cell
96+
97+
def append_cell(self, value: Dict[str, Any], txn=None) -> None:
98+
ycell = self.create_ycell(value)
99+
if txn is None:
100+
with self._ydoc.begin_transaction() as txn:
101+
self._ycells.append(txn, ycell)
102+
else:
103+
self._ycells.append(txn, ycell)
104+
105+
def set_cell(self, index: int, value: Dict[str, Any], txn=None) -> None:
106+
ycell = self.create_ycell(value)
107+
self.set_ycell(index, ycell, txn)
108+
109+
def create_ycell(self, value: Dict[str, Any]) -> None:
110+
cell = copy.deepcopy(value)
111+
if "id" not in cell:
112+
cell["id"] = str(uuid4())
113+
cell_type = cell["cell_type"]
114+
cell["source"] = Y.YText(cell["source"])
115+
cell["metadata"] = Y.YMap(cell.get("metadata", {}))
116+
if cell_type in ("raw", "markdown"):
117+
cell["attachments"] = Y.YMap(cell.get("attachments", {}))
118+
elif cell_type == "code":
119+
cell["outputs"] = Y.YArray(cell.get("outputs", []))
120+
return Y.YMap(cell)
121+
122+
def set_ycell(self, index: int, ycell: Y.YMap, txn=None):
123+
if txn is None:
124+
with self._ydoc.begin_transaction() as txn:
125+
self._ycells.delete(txn, index)
126+
self._ycells.insert(txn, index, ycell)
127+
else:
128+
self._ycells.delete(txn, index)
129+
self._ycells.insert(txn, index, ycell)
130+
131+
def get(self):
84132
meta = self._ymeta.to_json()
85-
cells = self._ycells.to_json()
86133
cast_all(meta, float, int)
87-
cast_all(cells, float, int)
88-
for cell in cells:
134+
cells = []
135+
for i in range(len(self._ycells)):
136+
cell = self.get_cell(i)
89137
if "id" in cell and meta["nbformat"] == 4 and meta["nbformat_minor"] <= 4:
90138
# strip cell IDs if we have notebook format 4.0-4.4
91139
del cell["id"]
92140
if cell["cell_type"] in ["raw", "markdown"] and not cell["attachments"]:
93141
del cell["attachments"]
142+
cells.append(cell)
94143

95144
return dict(
96145
cells=cells,
@@ -99,24 +148,22 @@ def source(self):
99148
nbformat_minor=int(meta["nbformat_minor"]),
100149
)
101150

102-
@source.setter
103-
def source(self, value):
104-
nb = copy.deepcopy(value)
151+
def set(self, value):
152+
nb_without_cells = {key: value[key] for key in value.keys() if key != "cells"}
153+
nb = copy.deepcopy(nb_without_cells)
105154
cast_all(nb, int, float)
106-
if not nb["cells"]:
107-
nb["cells"] = [
108-
{
109-
"cell_type": "code",
110-
"execution_count": None,
111-
"metadata": {},
112-
"outputs": [],
113-
"source": "",
114-
"id": str(uuid4()),
115-
}
116-
]
155+
cells = value["cells"] or [
156+
{
157+
"cell_type": "code",
158+
"execution_count": None,
159+
"metadata": {},
160+
"outputs": [],
161+
"source": "",
162+
"id": str(uuid4()),
163+
}
164+
]
117165
with self._ydoc.begin_transaction() as t:
118166
# clear document
119-
# TODO: use clear
120167
cells_len = len(self._ycells)
121168
for key in self._ymeta:
122169
self._ymeta.pop(t, key)
@@ -126,29 +173,7 @@ def source(self, value):
126173
self._ystate.pop(t, key)
127174

128175
# initialize document
129-
ycells = []
130-
for cell in nb["cells"]:
131-
if "id" not in cell:
132-
cell["id"] = str(uuid4())
133-
cell_type = cell["cell_type"]
134-
cell["source"] = Y.YText(cell["source"])
135-
metadata = {}
136-
if "metadata" in cell:
137-
metadata = cell["metadata"]
138-
cell["metadata"] = Y.YMap(metadata)
139-
if cell_type in ["raw", "markdown"]:
140-
attachments = {}
141-
if "attachments" in cell:
142-
attachments = cell["attachments"]
143-
cell["attachments"] = Y.YMap(attachments)
144-
elif cell_type == "code":
145-
outputs = cell.get("outputs", [])
146-
cell["outputs"] = Y.YArray(outputs)
147-
ycell = Y.YMap(cell)
148-
ycells.append(ycell)
149-
150-
if ycells:
151-
self._ycells.extend(t, ycells)
176+
self._ycells.extend(t, [self.create_ycell(cell) for cell in cells])
152177
self._ymeta.set(t, "metadata", nb["metadata"])
153178
self._ymeta.set(t, "nbformat", nb["nbformat"])
154179
self._ymeta.set(t, "nbformat_minor", nb["nbformat_minor"])

setup.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ python_requires = >=3.7
2222

2323
install_requires =
2424
setuptools
25-
y-py >=0.5.2,<0.6.0
26-
ypy-websocket >=0.1.8
25+
y-py >=0.5.3,<0.6.0
26+
ypy-websocket >=0.3.1,<0.4.0
2727

2828
[options.extras_require]
2929
test =

tests/test_ypy_yjs.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from pathlib import Path
44

55
import pytest
6+
import y_py as Y
67
from websockets import connect # type: ignore
7-
from ypy_websocket import WebsocketProvider, YDoc
8+
from ypy_websocket import WebsocketProvider
89

910
from jupyter_ydoc import YNotebook
1011
from jupyter_ydoc.utils import cast_all
@@ -13,7 +14,7 @@
1314

1415

1516
class YTest:
16-
def __init__(self, ydoc: YDoc, timeout: float = 1.0):
17+
def __init__(self, ydoc: Y.YDoc, timeout: float = 1.0):
1718
self.timeout = timeout
1819
self.ytest = ydoc.get_map("_test")
1920
with ydoc.begin_transaction() as t:
@@ -37,7 +38,7 @@ def source(self):
3738
@pytest.mark.asyncio
3839
@pytest.mark.parametrize("yjs_client", "0", indirect=True)
3940
async def test_ypy_yjs_0(yws_server, yjs_client):
40-
ydoc = YDoc()
41+
ydoc = Y.YDoc()
4142
ynotebook = YNotebook(ydoc)
4243
websocket = await connect("ws://localhost:1234/my-roomname")
4344
WebsocketProvider(ydoc, websocket)

0 commit comments

Comments
 (0)