From 59131914caf27c4e44d5cf0ef90d6442a0aadf7f Mon Sep 17 00:00:00 2001 From: martinRenou Date: Tue, 19 Dec 2023 15:37:06 +0100 Subject: [PATCH] Jupyter Collaboration v2 --- packages/base/package.json | 4 +- packages/schema/package.json | 2 +- python/jupytercad_app/package.json | 4 +- .../jupytercad_core/jcad_ydoc.py | 45 +++++++--------- .../jupytercad_core/step_ydoc.py | 8 +-- python/jupytercad_core/package.json | 2 +- python/jupytercad_core/pyproject.toml | 4 +- .../jupytercad_lab/notebook/cad_document.py | 48 ++++++++--------- .../jupytercad_lab/notebook/y_connector.py | 2 +- python/jupytercad_lab/package.json | 2 +- python/jupytercad_lab/pyproject.toml | 6 +-- yarn.lock | 52 +++++++++---------- 12 files changed, 82 insertions(+), 97 deletions(-) diff --git a/packages/base/package.json b/packages/base/package.json index 970ba565..1144b3dc 100644 --- a/packages/base/package.json +++ b/packages/base/package.json @@ -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", diff --git a/packages/schema/package.json b/packages/schema/package.json index 79d576e3..f5dec9ff 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -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", diff --git a/python/jupytercad_app/package.json b/python/jupytercad_app/package.json index 39abca74..46b1a954 100644 --- a/python/jupytercad_app/package.json +++ b/python/jupytercad_app/package.json @@ -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", diff --git a/python/jupytercad_core/jupytercad_core/jcad_ydoc.py b/python/jupytercad_core/jupytercad_core/jcad_ydoc.py index 57940db4..4fd1d3bb 100644 --- a/python/jupytercad_core/jupytercad_core/jcad_ydoc.py +++ b/python/jupytercad_core/jupytercad_core/jcad_ydoc.py @@ -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" @@ -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, @@ -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() @@ -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()) diff --git a/python/jupytercad_core/jupytercad_core/step_ydoc.py b/python/jupytercad_core/jupytercad_core/step_ydoc.py index 2435d770..5d658672 100644 --- a/python/jupytercad_core/jupytercad_core/step_ydoc.py +++ b/python/jupytercad_core/jupytercad_core/step_ydoc.py @@ -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" @@ -19,7 +20,7 @@ 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: """ @@ -27,8 +28,7 @@ def set(self, value: str) -> None: :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() diff --git a/python/jupytercad_core/package.json b/python/jupytercad_core/package.json index dc2a039d..e419dc52 100644 --- a/python/jupytercad_core/package.json +++ b/python/jupytercad_core/package.json @@ -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", diff --git a/python/jupytercad_core/pyproject.toml b/python/jupytercad_core/pyproject.toml index c8cae945..06987d94 100644 --- a/python/jupytercad_core/pyproject.toml +++ b/python/jupytercad_core/pyproject.toml @@ -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"} diff --git a/python/jupytercad_lab/jupytercad_lab/notebook/cad_document.py b/python/jupytercad_lab/jupytercad_lab/notebook/cad_document.py index 8d3d47cc..8fe672dc 100644 --- a/python/jupytercad_lab/jupytercad_lab/notebook/cad_document.py +++ b/python/jupytercad_lab/jupytercad_lab/notebook/cad_document.py @@ -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 @@ -32,7 +32,7 @@ logger = logging.getLogger(__file__) -class CadDocument(Widget): +class CadDocument(CommWidget): """ Create a new CadDocument object. @@ -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]: @@ -88,18 +86,18 @@ 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 @@ -107,9 +105,8 @@ 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 @@ -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, @@ -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: @@ -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( @@ -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 @@ -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 @@ -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: diff --git a/python/jupytercad_lab/jupytercad_lab/notebook/y_connector.py b/python/jupytercad_lab/jupytercad_lab/notebook/y_connector.py index 45da85de..bfd45f10 100644 --- a/python/jupytercad_lab/jupytercad_lab/notebook/y_connector.py +++ b/python/jupytercad_lab/jupytercad_lab/notebook/y_connector.py @@ -1,7 +1,7 @@ import logging from typing import Optional -from ypywidgets.ypywidgets import Widget +from ypywidgets import Widget logger = logging.getLogger(__file__) diff --git a/python/jupytercad_lab/package.json b/python/jupytercad_lab/package.json index 06f8f3f4..d4fb5ed0 100644 --- a/python/jupytercad_lab/package.json +++ b/python/jupytercad_lab/package.json @@ -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", diff --git a/python/jupytercad_lab/pyproject.toml b/python/jupytercad_lab/pyproject.toml index 3afd6bc4..9f17bf06 100644 --- a/python/jupytercad_lab/pyproject.toml +++ b/python/jupytercad_lab/pyproject.toml @@ -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", diff --git a/yarn.lock b/yarn.lock index 4bf08cd0..c34731a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -819,41 +819,41 @@ __metadata: languageName: node linkType: hard -"@jupyter/collaboration@npm:^1.0.0": - version: 1.2.1 - resolution: "@jupyter/collaboration@npm:1.2.1" +"@jupyter/collaboration@npm:^2.0.0": + version: 2.0.1 + resolution: "@jupyter/collaboration@npm:2.0.1" dependencies: "@codemirror/state": ^6.2.0 "@codemirror/view": ^6.7.0 - "@jupyter/docprovider": ^1.2.1 - "@jupyterlab/apputils": ^4.0.0 - "@jupyterlab/coreutils": ^6.0.0 - "@jupyterlab/services": ^7.0.0 - "@jupyterlab/ui-components": ^4.0.0 + "@jupyter/docprovider": ^2.0.1 + "@jupyterlab/apputils": ^4.0.5 + "@jupyterlab/coreutils": ^6.0.5 + "@jupyterlab/services": ^7.0.5 + "@jupyterlab/ui-components": ^4.0.5 "@lumino/coreutils": ^2.1.0 "@lumino/virtualdom": ^2.0.0 "@lumino/widgets": ^2.1.0 react: ^18.2.0 y-protocols: ^1.0.5 yjs: ^13.5.40 - checksum: f38885112c337415df963782653866a058b68d1509faed9ca092c3d729cd27cdecb3e36365e49b78135d626a4d76c7c5479601a997f76142d61e7edf41015fc3 + checksum: 77a2b677de3bb476758ae02a801f34fb68f23c1e9bcfd335450e794f29993bedf76e2bc96beae6fb607b4ba7014f5a278a24941826747ed285f9c3dcef672e33 languageName: node linkType: hard -"@jupyter/docprovider@npm:^1.0.0, @jupyter/docprovider@npm:^1.2.1": - version: 1.2.1 - resolution: "@jupyter/docprovider@npm:1.2.1" +"@jupyter/docprovider@npm:^2.0.0, @jupyter/docprovider@npm:^2.0.1": + version: 2.0.1 + resolution: "@jupyter/docprovider@npm:2.0.1" dependencies: - "@jupyter/ydoc": ^1.0.2 - "@jupyterlab/coreutils": ^6.0.0 - "@jupyterlab/services": ^7.0.0 + "@jupyter/ydoc": ^1.1.0-a0 + "@jupyterlab/coreutils": ^6.0.5 + "@jupyterlab/services": ^7.0.5 "@lumino/coreutils": ^2.1.0 "@lumino/disposable": ^2.1.0 "@lumino/signaling": ^2.1.0 y-protocols: ^1.0.5 y-websocket: ^1.3.15 yjs: ^13.5.40 - checksum: 5a8ae37ec44f39754e30f0d7d697a0147bc15b2e9fe37aa98770971dfad77724cf83177220841e4a1ad21a2f5b021fc21bac95499e6476b281ec9491fd3a89b1 + checksum: 84bc5ce9a100de8bb976e5775c7d891e6a4c3c2f1d8cb3343c44615e291ca4df89784ece8b114d07820ee6ccbd368f0e06208e11db380284e61a3d7e0d993897 languageName: node linkType: hard @@ -880,7 +880,7 @@ __metadata: languageName: node linkType: hard -"@jupyter/ydoc@npm:^0.3.4 || ^1.0.2, @jupyter/ydoc@npm:^1.0.2, @jupyter/ydoc@npm:^1.1.1": +"@jupyter/ydoc@npm:^0.3.4 || ^1.0.2, @jupyter/ydoc@npm:^1.0.0, @jupyter/ydoc@npm:^1.0.2, @jupyter/ydoc@npm:^1.1.0-a0, @jupyter/ydoc@npm:^1.1.1": version: 1.1.1 resolution: "@jupyter/ydoc@npm:1.1.1" dependencies: @@ -914,8 +914,8 @@ __metadata: dependencies: "@apidevtools/json-schema-ref-parser": ^9.0.9 "@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 @@ -956,8 +956,8 @@ __metadata: 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 @@ -1016,7 +1016,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jupytercad/jupytercad-core@workspace:python/jupytercad_core" 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 @@ -1051,7 +1051,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jupytercad/jupytercad-lab@workspace:python/jupytercad_lab" 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 @@ -1140,7 +1140,7 @@ __metadata: resolution: "@jupytercad/schema@workspace:packages/schema" 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 @@ -1530,7 +1530,7 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/coreutils@npm:^6.0.0, @jupyterlab/coreutils@npm:^6.0.0-alpha.18, @jupyterlab/coreutils@npm:^6.0.12, @jupyterlab/coreutils@npm:^6.1.0": +"@jupyterlab/coreutils@npm:^6.0.0, @jupyterlab/coreutils@npm:^6.0.0-alpha.18, @jupyterlab/coreutils@npm:^6.0.12, @jupyterlab/coreutils@npm:^6.0.5, @jupyterlab/coreutils@npm:^6.1.0": version: 6.1.0 resolution: "@jupyterlab/coreutils@npm:6.1.0" dependencies: @@ -2229,7 +2229,7 @@ __metadata: languageName: node linkType: hard -"@jupyterlab/ui-components@npm:^4.0.0, @jupyterlab/ui-components@npm:^4.0.0-alpha.33, @jupyterlab/ui-components@npm:^4.0.12, @jupyterlab/ui-components@npm:^4.1.0": +"@jupyterlab/ui-components@npm:^4.0.0, @jupyterlab/ui-components@npm:^4.0.0-alpha.33, @jupyterlab/ui-components@npm:^4.0.12, @jupyterlab/ui-components@npm:^4.0.5, @jupyterlab/ui-components@npm:^4.1.0": version: 4.1.0 resolution: "@jupyterlab/ui-components@npm:4.1.0" dependencies: