Skip to content

Commit

Permalink
Jupyter Collaboration v2 (#239)
Browse files Browse the repository at this point in the history
  • Loading branch information
martinRenou authored Feb 8, 2024
1 parent eab781e commit 962f123
Show file tree
Hide file tree
Showing 12 changed files with 82 additions and 97 deletions.
4 changes: 2 additions & 2 deletions packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
},
"dependencies": {
"@deathbeds/jupyterlab-rjsf": "^1.1.0",
"@jupyter/docprovider": "^1.0.0",
"@jupyter/ydoc": "^0.3.4 || ^1.0.2",
"@jupyter/docprovider": "^2.0.0",
"@jupyter/ydoc": "^1.0.0",
"@jupytercad/occ-worker": "^1.0.1",
"@jupytercad/schema": "^1.0.1",
"@jupyterlab/application": "^4.0.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
},
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^9.0.9",
"@jupyter/ydoc": "^0.3.4 || ^1.0.2",
"@jupyter/ydoc": "^1.0.0",
"@jupyterlab/apputils": "^4.0.0",
"@jupyterlab/coreutils": "^6.0.0",
"@jupyterlab/docregistry": "^4.0.0",
Expand Down
4 changes: 2 additions & 2 deletions python/jupytercad_app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
"dependencies": {
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.9.3",
"@jupyter/collaboration": "^1.0.0",
"@jupyter/docprovider": "^1.0.0",
"@jupyter/collaboration": "^2.0.0",
"@jupyter/docprovider": "^2.0.0",
"@jupyter/ydoc": "^0.3.4 || ^1.0.2",
"@jupytercad/base": "^1.0.1",
"@jupytercad/schema": "^1.0.1",
Expand Down
45 changes: 18 additions & 27 deletions python/jupytercad_core/jupytercad_core/jcad_ydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
from typing import Any, Callable
from functools import partial

import y_py as Y
from pycrdt import Array, Map, Text
from jupyter_ydoc.ybasedoc import YBaseDoc


class YJCad(YBaseDoc):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._ysource = self._ydoc.get_text("source")
self._yobjects = self._ydoc.get_array("objects")
self._yoptions = self._ydoc.get_map("options")
self._ymeta = self._ydoc.get_map("metadata")
self._youtputs = self._ydoc.get_map("outputs")
self._ydoc["source"] = self._ysource = Text()
self._ydoc["objects"] = self._yobjects = Array()
self._ydoc["options"] = self._yoptions = Map()
self._ydoc["metadata"] = self._ymetadata = Map()
self._ydoc["outputs"] = self._youtputs = Map()

def version(self) -> str:
return "0.1.0"
Expand All @@ -24,10 +24,10 @@ def get(self) -> str:
:return: Document's content.
:rtype: Any
"""
objects = json.loads(self._yobjects.to_json())
options = json.loads(self._yoptions.to_json())
meta = json.loads(self._ymeta.to_json())
outputs = json.loads(self._youtputs.to_json())
objects = self._yobjects.to_py()
options = self._yoptions.to_py()
meta = self._ymetadata.to_py()
outputs = self._youtputs.to_py()
return json.dumps(
dict(objects=objects, options=options, metadata=meta, outputs=outputs),
indent=2,
Expand All @@ -42,18 +42,14 @@ def set(self, value: str) -> None:
valueDict = json.loads(value)
newObj = []
for obj in valueDict["objects"]:
newObj.append(Y.YMap(obj))
with self._ydoc.begin_transaction() as t:
length = len(self._yobjects)
self._yobjects.delete_range(t, 0, length)
# workaround for https://github.com/y-crdt/ypy/issues/126:
# self._yobjects.extend(t, newObj)
for o in newObj:
self._yobjects.append(t, o)
newObj.append(Map(obj))

self._replace_y_map(t, self._yoptions, valueDict["options"])
self._replace_y_map(t, self._ymeta, valueDict["metadata"])
self._replace_y_map(t, self._youtputs, valueDict.get("outputs", {}))
self._yobjects.clear()
self._yobjects.extend(newObj)

self._yoptions.update(valueDict["options"])
self._ymetadata.update(valueDict["metadata"])
self._youtputs.update(valueDict["outputs"])

def observe(self, callback: Callable[[str, Any], None]):
self.unobserve()
Expand All @@ -69,11 +65,6 @@ def observe(self, callback: Callable[[str, Any], None]):
self._subscriptions[self._yoptions] = self._yoptions.observe_deep(
partial(callback, "options")
)
self._subscriptions[self._ymeta] = self._ymeta.observe_deep(
self._subscriptions[self._ymetadata] = self._ymetadata.observe_deep(
partial(callback, "meta")
)

def _replace_y_map(self, t: Y.YTransaction, y_map: Y.YMap, new_value: dict):
for key in y_map:
y_map.pop(t, key)
y_map.update(t, new_value.items())
8 changes: 4 additions & 4 deletions python/jupytercad_core/jupytercad_core/step_ydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
from typing import Any, Callable
from functools import partial

from pycrdt import Text
from jupyter_ydoc.ybasedoc import YBaseDoc


class YSTEP(YBaseDoc):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._ysource = self._ydoc.get_text("source")
self._ydoc["source"] = self._ysource = Text()

def version(self) -> str:
return "0.1.0"
Expand All @@ -19,16 +20,15 @@ def get(self) -> str:
:return: Document's content.
:rtype: Any
"""
return json.dumps(self._ysource.to_json())
return json.dumps(self._ysource.to_py())

def set(self, value: str) -> None:
"""
Sets the content of the document.
:param value: The content of the document.
:type value: Any
"""
with self._ydoc.begin_transaction() as t:
self._ysource.extend(t, value)
self._ysource[:] = value

def observe(self, callback: Callable[[str, Any], None]):
self.unobserve()
Expand Down
2 changes: 1 addition & 1 deletion python/jupytercad_core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"build:worker:prod": "webpack --config worker.webpack.config.js --mode=production"
},
"dependencies": {
"@jupyter/docprovider": "^1.0.0",
"@jupyter/docprovider": "^2.0.0",
"@jupytercad/base": "^1.0.1",
"@jupytercad/occ-worker": "^1.0.1",
"@jupytercad/schema": "^1.0.1",
Expand Down
4 changes: 2 additions & 2 deletions python/jupytercad_core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ classifiers = [
]
dependencies = [
"jupyter_server>=2.0.6,<3",
"jupyter_ydoc>=1.1.0,<2",
"jupyter_ydoc>=2,<3",
"y-py>=0.6.0,<0.7",
"jupyter-collaboration>=1.2.0,<2",
"jupyter-collaboration>=2,<3",
]
dynamic = ["version", "description", "authors", "urls", "keywords"]
license = {file = "LICENSE"}
Expand Down
48 changes: 21 additions & 27 deletions python/jupytercad_lab/jupytercad_lab/notebook/cad_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from pathlib import Path
from typing import Any, Dict, List, Optional, Union

import y_py as Y
from pycrdt import Array, Doc, Map
from pydantic import BaseModel
from ypywidgets.ypywidgets import Widget
from ypywidgets.comm import CommWidget

from .objects._schema.any import IAny
from uuid import uuid4
Expand All @@ -32,7 +32,7 @@
logger = logging.getLogger(__file__)


class CadDocument(Widget):
class CadDocument(CommWidget):
"""
Create a new CadDocument object.
Expand All @@ -43,19 +43,17 @@ class CadDocument(Widget):
def __init__(self, path: Optional[str] = None):
comm_metadata = CadDocument._path_to_comm(path)

ydoc = Y.YDoc()
ydoc = Doc()

super().__init__(
comm_metadata=dict(ymodel_name="@jupytercad:widget", **comm_metadata),
ydoc=ydoc,
)

self.ydoc = ydoc

self._objects_array = self.ydoc.get_array("objects")
self._metadata = self.ydoc.get_map("metadata")
self._outputs = self.ydoc.get_map("outputs")
self._options = self.ydoc.get_map("options")
self.ydoc["objects"] = self._objects_array = Array()
self.ydoc["metadata"] = self._metadata = Map()
self.ydoc["outputs"] = self._outputs = Map()
self.ydoc["options"] = self._options = Map()

@property
def objects(self) -> List[str]:
Expand Down Expand Up @@ -88,28 +86,27 @@ def _path_to_comm(cls, filePath: Optional[str]) -> Dict:
else:
raise ValueError("File extension is not supported!")
return dict(
path=path, format=format, contentType=contentType, create_ydoc=path is None
path=path, format=format, contentType=contentType, createydoc=path is None
)

def get_object(self, name: str) -> Optional["PythonJcadObject"]:
if self.check_exist(name):
data = json.loads(self._get_yobject_by_name(name).to_json())
data = json.loads(self._get_yobject_by_name(name).to_py())
return OBJECT_FACTORY.create_object(data, self)

def remove(self, name: str) -> CadDocument:
index = self._get_yobject_index_by_name(name)
if self._objects_array and index != -1:
with self.ydoc.begin_transaction() as t:
with self.ydoc.transaction() as t:
self._objects_array.delete(t, index)
return self

def add_object(self, new_object: "PythonJcadObject") -> CadDocument:
if self._objects_array is not None and not self.check_exist(new_object.name):
obj_dict = json.loads(new_object.json())
obj_dict["visible"] = True
new_map = Y.YMap(obj_dict)
with self.ydoc.begin_transaction() as t:
self._objects_array.append(t, new_map)
new_map = Map(obj_dict)
self._objects_array.append(new_map)
else:
logger.error(f"Object {new_object.name} already exists")
return self
Expand Down Expand Up @@ -144,7 +141,7 @@ def add_annotation(
)
contents = [{"user": user, "value": message}]
if self._metadata is not None:
with self.ydoc.begin_transaction() as t:
with self.ydoc.transaction() as t:
self._metadata.set(
t,
new_id,
Expand All @@ -165,7 +162,7 @@ def remove_annotation(self, annotation_id: str) -> None:
:param annotation_id: The id of the annotation
"""
if self._metadata is not None:
with self.ydoc.begin_transaction() as t:
with self.ydoc.transaction() as t:
self._metadata.pop(t, annotation_id, None)

def set_color(self, object_name: str, color: Optional[List]) -> None:
Expand All @@ -191,7 +188,7 @@ def set_color(self, object_name: str, color: Optional[List]) -> None:
if color is not None:
new_gui = {object_name: {"color": color}}
if new_gui is not None:
with self.ydoc.begin_transaction() as t:
with self.ydoc.transaction() as t:
self._options.set(t, "guidata", new_gui)

def add_step_file(
Expand Down Expand Up @@ -225,8 +222,7 @@ def add_step_file(
"visible": True,
}

with self.ydoc.begin_transaction() as t:
self._objects_array.append(t, Y.YMap(data))
self._objects_array.append(Map(data))

return self

Expand Down Expand Up @@ -278,8 +274,7 @@ def add_occ_shape(
"visible": True,
}

with self.ydoc.begin_transaction() as t:
self._objects_array.append(t, Y.YMap(data))
self._objects_array.append(Map(data))

return self

Expand Down Expand Up @@ -625,20 +620,19 @@ def _get_boolean_operands(self, shape1: str | int | None, shape2: str | int | No
return shape1, shape2

def set_visible(self, name: str, value):
obj: Optional[Y.YMap] = self._get_yobject_by_name(name)
obj: Optional[Map] = self._get_yobject_by_name(name)

if obj is None:
raise RuntimeError(f"No object named {name}")

with self.ydoc.begin_transaction() as t:
obj.set(t, "visible", False)
obj["visible"] = False

def check_exist(self, name: str) -> bool:
if self.objects:
return name in self.objects
return False

def _get_yobject_by_name(self, name: str) -> Optional[Y.YMap]:
def _get_yobject_by_name(self, name: str) -> Optional[Map]:
if self._objects_array:
for index, item in enumerate(self._objects_array):
if item["name"] == name:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from typing import Optional

from ypywidgets.ypywidgets import Widget
from ypywidgets import Widget

logger = logging.getLogger(__file__)

Expand Down
2 changes: 1 addition & 1 deletion python/jupytercad_lab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"watch:labextension": "jupyter labextension watch ."
},
"dependencies": {
"@jupyter/docprovider": "^1.0.0",
"@jupyter/docprovider": "^2.0.0",
"@jupytercad/base": "^1.0.1",
"@jupytercad/jupytercad-core": "^1.0.1",
"@jupytercad/schema": "^1.0.1",
Expand Down
6 changes: 3 additions & 3 deletions python/jupytercad_lab/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ classifiers = [
]
dependencies = [
"jupyter_server>=2.0.1,<3",
"jupyter_ydoc>=1.1.0,<2",
"jupyter-ydoc>=2,<3",
"y-py>=0.6.0,<0.7",
"jupyterlab>=4,<5",
"jupyter_collaboration>=1.0.0a9,<2",
"ypywidgets>=0.4.1,<0.5.0",
"jupyter-collaboration>=2,<3",
"ypywidgets>=0.6.5,<0.7",
"yjs-widgets>=0.3.4,<0.4",
"comm>=0.1.2,<0.2.0",
"pydantic>=2,<3",
Expand Down
Loading

0 comments on commit 962f123

Please sign in to comment.