Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use pycrdt instead of y-py #194

Merged
merged 4 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 17 additions & 19 deletions jupyter_ydoc/ybasedoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
# Distributed under the terms of the Modified BSD License.

from abc import ABC, abstractmethod
from typing import Any, Callable, Optional
from typing import Any, Callable, Dict, Optional

import y_py as Y
from pycrdt import Doc, Map


class YBaseDoc(ABC):
Expand All @@ -15,19 +15,19 @@ class YBaseDoc(ABC):
subscribe to changes in the document.
"""

def __init__(self, ydoc: Optional[Y.YDoc] = None):
def __init__(self, ydoc: Optional[Doc] = None):
"""
Constructs a YBaseDoc.

:param ydoc: The :class:`y_py.YDoc` that will hold the data of the document, if provided.
:type ydoc: :class:`y_py.YDoc`, optional.
:param ydoc: The :class:`pycrdt.Doc` that will hold the data of the document, if provided.
:type ydoc: :class:`pycrdt.Doc`, optional.
"""
if ydoc is None:
self._ydoc = Y.YDoc()
self._ydoc = Doc()
else:
self._ydoc = ydoc
self._ystate = self._ydoc.get_map("state")
self._subscriptions = {}
self._ydoc["state"] = self._ystate = Map()
self._subscriptions: Dict[Any, str] = {}

@property
@abstractmethod
Expand All @@ -40,22 +40,22 @@ def version(self) -> str:
"""

@property
def ystate(self) -> Y.YMap:
def ystate(self) -> Map:
"""
A :class:`y_py.YMap` containing the state of the document.
A :class:`pycrdt.Map` containing the state of the document.

:return: The document's state.
:rtype: :class:`y_py.YMap`
:rtype: :class:`pycrdt.Map`
"""
return self._ystate

@property
def ydoc(self) -> Y.YDoc:
def ydoc(self) -> Doc:
"""
The underlying :class:`y_py.YDoc` that contains the data.
The underlying :class:`pycrdt.Doc` that contains the data.

:return: The document's ydoc.
:rtype: :class:`y_py.YDoc`
:rtype: :class:`pycrdt.Doc`
"""
return self._ydoc

Expand Down Expand Up @@ -87,7 +87,7 @@ def dirty(self) -> Optional[bool]:
:return: Whether the document is dirty.
:rtype: Optional[bool]
"""
return self._ystate["dirty"]
return self._ystate.get("dirty")

@dirty.setter
def dirty(self, value: bool) -> None:
Expand All @@ -97,8 +97,7 @@ def dirty(self, value: bool) -> None:
:param value: Whether the document is clean or dirty.
:type value: bool
"""
with self._ydoc.begin_transaction() as t:
self._ystate.set(t, "dirty", value)
self._ystate["dirty"] = value
davidbrochart marked this conversation as resolved.
Show resolved Hide resolved

@property
def path(self) -> Optional[str]:
Expand All @@ -118,8 +117,7 @@ def path(self, value: str) -> None:
:param value: Document's path.
:type value: str
"""
with self._ydoc.begin_transaction() as t:
self._ystate.set(t, "path", value)
self._ystate["path"] = value

@abstractmethod
def get(self) -> Any:
Expand Down
13 changes: 6 additions & 7 deletions jupyter_ydoc/yblob.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from functools import partial
from typing import Any, Callable, Optional, Union

import y_py as Y
from pycrdt import Doc, Map

from .ybasedoc import YBaseDoc

Expand All @@ -28,15 +28,15 @@ class YBlob(YBaseDoc):
}
"""

def __init__(self, ydoc: Optional[Y.YDoc] = None):
def __init__(self, ydoc: Optional[Doc] = None):
"""
Constructs a YBlob.

:param ydoc: The :class:`y_py.YDoc` that will hold the data of the document, if provided.
:type ydoc: :class:`y_py.YDoc`, optional.
:param ydoc: The :class:`pycrdt.Doc` that will hold the data of the document, if provided.
:type ydoc: :class:`pycrdt.Doc`, optional.
"""
super().__init__(ydoc)
self._ysource = self._ydoc.get_map("source")
self._ydoc["source"] = self._ysource = Map()

@property
def version(self) -> str:
Expand Down Expand Up @@ -66,8 +66,7 @@ def set(self, value: Union[bytes, str]) -> None:
"""
if isinstance(value, bytes):
value = base64.b64encode(value).decode()
with self._ydoc.begin_transaction() as t:
self._ysource.set(t, "base64", value)
self._ysource["base64"] = value

def observe(self, callback: Callable[[str, Any], None]) -> None:
"""
Expand Down
93 changes: 33 additions & 60 deletions jupyter_ydoc/ynotebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Any, Callable, Dict, Optional
from uuid import uuid4

import y_py as Y
from pycrdt import Array, Doc, Map, Text

from .utils import cast_all
from .ybasedoc import YBaseDoc
Expand Down Expand Up @@ -47,16 +47,16 @@ class YNotebook(YBaseDoc):
}
"""

def __init__(self, ydoc: Optional[Y.YDoc] = None):
def __init__(self, ydoc: Optional[Doc] = None):
"""
Constructs a YNotebook.

:param ydoc: The :class:`y_py.YDoc` that will hold the data of the document, if provided.
:type ydoc: :class:`y_py.YDoc`, optional.
:param ydoc: The :class:`pycrdt.Doc` that will hold the data of the document, if provided.
:type ydoc: :class:`pycrdt.Doc`, optional.
"""
super().__init__(ydoc)
self._ymeta = self._ydoc.get_map("meta")
self._ycells = self._ydoc.get_array("cells")
self._ydoc["meta"] = self._ymeta = Map()
self._ydoc["cells"] = self._ycells = Array()

@property
def version(self) -> str:
Expand All @@ -74,7 +74,7 @@ def ycells(self):
Returns the Y-cells.

:return: The Y-cells.
:rtype: :class:`y_py.YArray`
:rtype: :class:`pycrdt.Array`
"""
return self._ycells

Expand All @@ -98,8 +98,8 @@ def get_cell(self, index: int) -> Dict[str, Any]:
:return: A cell.
:rtype: Dict[str, Any]
"""
meta = json.loads(self._ymeta.to_json())
cell = json.loads(self._ycells[index].to_json())
meta = json.loads(str(self._ymeta))
davidbrochart marked this conversation as resolved.
Show resolved Hide resolved
cell = json.loads(str(self._ycells[index]))
cast_all(cell, float, int) # cells coming from Yjs have e.g. execution_count as float
if "id" in cell and meta["nbformat"] == 4 and meta["nbformat_minor"] <= 4:
# strip cell IDs if we have notebook format 4.0-4.4
Expand All @@ -112,26 +112,17 @@ def get_cell(self, index: int) -> Dict[str, Any]:
del cell["attachments"]
return cell

def append_cell(self, value: Dict[str, Any], txn: Optional[Y.YTransaction] = None) -> None:
def append_cell(self, value: Dict[str, Any]) -> None:
"""
Appends a cell.

:param value: A cell.
:type value: Dict[str, Any]

:param txn: A YTransaction, defaults to None
:type txn: :class:`y_py.YTransaction`, optional.
"""
ycell = self.create_ycell(value)
if txn is None:
with self._ydoc.begin_transaction() as txn:
self._ycells.append(txn, ycell)
else:
self._ycells.append(txn, ycell)

def set_cell(
self, index: int, value: Dict[str, Any], txn: Optional[Y.YTransaction] = None
) -> None:
self._ycells.append(ycell)

def set_cell(self, index: int, value: Dict[str, Any]) -> None:
"""
Sets a cell into indicated position.

Expand All @@ -140,60 +131,48 @@ def set_cell(

:param value: A cell.
:type value: Dict[str, Any]

:param txn: A YTransaction, defaults to None
:type txn: :class:`y_py.YTransaction`, optional.
"""
ycell = self.create_ycell(value)
self.set_ycell(index, ycell, txn)
self.set_ycell(index, ycell)

def create_ycell(self, value: Dict[str, Any]) -> Y.YMap:
def create_ycell(self, value: Dict[str, Any]) -> Map:
"""
Creates YMap with the content of the cell.

:param value: A cell.
:type value: Dict[str, Any]

:return: A new cell.
:rtype: :class:`y_py.YMap`
:rtype: :class:`pycrdt.Map`
"""
cell = copy.deepcopy(value)
if "id" not in cell:
cell["id"] = str(uuid4())
cell_type = cell["cell_type"]
cell_source = cell["source"]
cell_source = "".join(cell_source) if isinstance(cell_source, list) else cell_source
cell["source"] = Y.YText(cell_source)
cell["metadata"] = Y.YMap(cell.get("metadata", {}))
cell["source"] = Text(cell_source)
cell["metadata"] = Map(cell.get("metadata", {}))

if cell_type in ("raw", "markdown"):
if "attachments" in cell and not cell["attachments"]:
del cell["attachments"]
elif cell_type == "code":
cell["outputs"] = Y.YArray(cell.get("outputs", []))
cell["outputs"] = Array(cell.get("outputs", []))

return Y.YMap(cell)
return Map(cell)

def set_ycell(self, index: int, ycell: Y.YMap, txn: Optional[Y.YTransaction] = None) -> None:
def set_ycell(self, index: int, ycell: Map) -> None:
"""
Sets a Y cell into the indicated position.

:param index: The index of the cell.
:type index: int

:param ycell: A YMap with the content of a cell.
:type ycell: :class:`y_py.YMap`

:param txn: A YTransaction, defaults to None
:type txn: :class:`y_py.YTransaction`, optional.
:type ycell: :class:`pycrdt.Map`
"""
if txn is None:
with self._ydoc.begin_transaction() as txn:
self._ycells.delete(txn, index)
self._ycells.insert(txn, index, ycell)
else:
self._ycells.delete(txn, index)
self._ycells.insert(txn, index, ycell)
self._ycells[index] = ycell

def get(self) -> Dict:
"""
Expand All @@ -202,7 +181,7 @@ def get(self) -> Dict:
:return: Document's content.
:rtype: Dict
"""
meta = json.loads(self._ymeta.to_json())
meta = json.loads(str(self._ymeta))
cast_all(meta, float, int) # notebook coming from Yjs has e.g. nbformat as float
cells = []
for i in range(len(self._ycells)):
Expand Down Expand Up @@ -247,29 +226,23 @@ def set(self, value: Dict) -> None:
}
]

with self._ydoc.begin_transaction() as t:
with self._ydoc.transaction():
# clear document
cells_len = len(self._ycells)
for key in self._ymeta:
self._ymeta.pop(t, key)
if cells_len:
self._ycells.delete_range(t, 0, cells_len)
for key in [k for k in self._ystate if k not in ("dirty", "path")]:
self._ystate.pop(t, key)
self._ymeta.clear()
self._ycells.clear()
for key in [k for k in self._ystate.keys() if k not in ("dirty", "path")]:
del self._ystate[key]

# initialize document
# workaround for https://github.com/y-crdt/ypy/issues/126:
# self._ycells.extend(t, [self.create_ycell(cell) for cell in cells])
for cell in cells:
self._ycells.append(t, self.create_ycell(cell))
self._ymeta.set(t, "nbformat", nb.get("nbformat", NBFORMAT_MAJOR_VERSION))
self._ymeta.set(t, "nbformat_minor", nb.get("nbformat_minor", NBFORMAT_MINOR_VERSION))
self._ycells.extend([self.create_ycell(cell) for cell in cells])
self._ymeta["nbformat"] = nb.get("nbformat", NBFORMAT_MAJOR_VERSION)
self._ymeta["nbformat_minor"] = nb.get("nbformat_minor", NBFORMAT_MINOR_VERSION)

metadata = nb.get("metadata", {})
metadata.setdefault("language_info", {"name": ""})
metadata.setdefault("kernelspec", {"name": "", "display_name": ""})

self._ymeta.set(t, "metadata", Y.YMap(metadata))
self._ymeta["metadata"] = Map(metadata)

def observe(self, callback: Callable[[str, Any], None]) -> None:
"""
Expand Down
18 changes: 8 additions & 10 deletions jupyter_ydoc/yunicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from functools import partial
from typing import Any, Callable, Optional

import y_py as Y
from pycrdt import Doc, Text

from .ybasedoc import YBaseDoc

Expand All @@ -23,15 +23,15 @@ class YUnicode(YBaseDoc):
}
"""

def __init__(self, ydoc: Optional[Y.YDoc] = None):
def __init__(self, ydoc: Optional[Doc] = None):
"""
Constructs a YUnicode.

:param ydoc: The :class:`y_py.YDoc` that will hold the data of the document, if provided.
:type ydoc: :class:`y_py.YDoc`, optional.
:param ydoc: The :class:`pycrdt.Doc` that will hold the data of the document, if provided.
:type ydoc: :class:`pycrdt.Doc`, optional.
"""
super().__init__(ydoc)
self._ysource = self._ydoc.get_text("source")
self._ydoc["source"] = self._ysource = Text()

@property
def version(self) -> str:
Expand Down Expand Up @@ -59,14 +59,12 @@ def set(self, value: str) -> None:
:param value: The content of the document.
:type value: str
"""
with self._ydoc.begin_transaction() as t:
with self._ydoc.transaction():
# clear document
source_len = len(self._ysource)
if source_len:
self._ysource.delete_range(t, 0, source_len)
self._ysource.clear()
# initialize document
if value:
self._ysource.extend(t, value)
self._ysource += value

def observe(self, callback: Callable[[str, Any], None]) -> None:
"""
Expand Down
Loading
Loading