jupyter-ydoc 3.1.0__py3-none-any.whl → 3.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jupyter_ydoc/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # This file is auto-generated by Hatchling. As such, do not:
2
2
  # - modify
3
3
  # - track in version control e.g. be sure to add to .gitignore
4
- __version__ = VERSION = '3.1.0'
4
+ __version__ = VERSION = '3.2.0'
jupyter_ydoc/ynotebook.py CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  import copy
5
5
  from functools import partial
6
- from typing import Any, Callable, Dict, Optional
6
+ from typing import Any, Callable, Dict, List, Optional
7
7
  from uuid import uuid4
8
8
 
9
9
  from pycrdt import Array, Awareness, Doc, Map, Text
@@ -102,8 +102,11 @@ class YNotebook(YBaseDoc):
102
102
  :return: A cell.
103
103
  :rtype: Dict[str, Any]
104
104
  """
105
+ return self._cell_to_py(self._ycells[index])
106
+
107
+ def _cell_to_py(self, ycell: Map) -> Dict[str, Any]:
105
108
  meta = self._ymeta.to_py()
106
- cell = self._ycells[index].to_py()
109
+ cell = ycell.to_py()
107
110
  cell.pop("execution_state", None)
108
111
  cast_all(cell, float, int) # cells coming from Yjs have e.g. execution_count as float
109
112
  if "id" in cell and meta["nbformat"] == 4 and meta["nbformat_minor"] <= 4:
@@ -234,7 +237,7 @@ class YNotebook(YBaseDoc):
234
237
  nb_without_cells = {key: value[key] for key in value.keys() if key != "cells"}
235
238
  nb = copy.deepcopy(nb_without_cells)
236
239
  cast_all(nb, int, float) # Yjs expects numbers to be floating numbers
237
- cells = value["cells"] or [
240
+ new_cells = value["cells"] or [
238
241
  {
239
242
  "cell_type": "code",
240
243
  "execution_count": None,
@@ -245,24 +248,69 @@ class YNotebook(YBaseDoc):
245
248
  "id": str(uuid4()),
246
249
  }
247
250
  ]
251
+ old_ycells_by_id = {ycell["id"]: ycell for ycell in self._ycells}
248
252
 
249
253
  with self._ydoc.transaction():
250
- # clear document
251
- self._ymeta.clear()
252
- self._ycells.clear()
253
- for key in [k for k in self._ystate.keys() if k not in ("dirty", "path")]:
254
+ new_cell_list: List[dict] = []
255
+ retained_cells = set()
256
+
257
+ # Determine cells to be retained
258
+ for new_cell in new_cells:
259
+ cell_id = new_cell.get("id")
260
+ if cell_id and (old_ycell := old_ycells_by_id.get(cell_id)):
261
+ old_cell = self._cell_to_py(old_ycell)
262
+ if old_cell == new_cell:
263
+ new_cell_list.append(old_cell)
264
+ retained_cells.add(cell_id)
265
+ continue
266
+ # New or changed cell
267
+ new_cell_list.append(new_cell)
268
+
269
+ # First delete all non-retained cells
270
+ if not retained_cells:
271
+ # fast path if no cells were retained
272
+ self._ycells.clear()
273
+ else:
274
+ index = 0
275
+ for old_ycell in list(self._ycells):
276
+ if old_ycell["id"] not in retained_cells:
277
+ self._ycells.pop(index)
278
+ else:
279
+ index += 1
280
+
281
+ # Now add new cells
282
+ index = 0
283
+ for new_cell in new_cell_list:
284
+ if len(self._ycells) > index:
285
+ if self._ycells[index]["id"] == new_cell.get("id"):
286
+ # retained cell
287
+ index += 1
288
+ continue
289
+ self._ycells.insert(index, self.create_ycell(new_cell))
290
+ index += 1
291
+
292
+ for key in [
293
+ k for k in self._ystate.keys() if k not in ("dirty", "path", "document_id")
294
+ ]:
254
295
  del self._ystate[key]
255
296
 
256
- # initialize document
257
- self._ycells.extend([self.create_ycell(cell) for cell in cells])
258
- self._ymeta["nbformat"] = nb.get("nbformat", NBFORMAT_MAJOR_VERSION)
259
- self._ymeta["nbformat_minor"] = nb.get("nbformat_minor", NBFORMAT_MINOR_VERSION)
297
+ nbformat_major = nb.get("nbformat", NBFORMAT_MAJOR_VERSION)
298
+ nbformat_minor = nb.get("nbformat_minor", NBFORMAT_MINOR_VERSION)
299
+
300
+ if self._ymeta.get("nbformat") != nbformat_major:
301
+ self._ymeta["nbformat"] = nbformat_major
302
+
303
+ if self._ymeta.get("nbformat_minor") != nbformat_minor:
304
+ self._ymeta["nbformat_minor"] = nbformat_minor
260
305
 
306
+ old_y_metadata = self._ymeta.get("metadata")
307
+ old_metadata = old_y_metadata.to_py() if old_y_metadata else {}
261
308
  metadata = nb.get("metadata", {})
262
- metadata.setdefault("language_info", {"name": ""})
263
- metadata.setdefault("kernelspec", {"name": "", "display_name": ""})
264
309
 
265
- self._ymeta["metadata"] = Map(metadata)
310
+ if metadata != old_metadata:
311
+ metadata.setdefault("language_info", {"name": ""})
312
+ metadata.setdefault("kernelspec", {"name": "", "display_name": ""})
313
+ self._ymeta["metadata"] = Map(metadata)
266
314
 
267
315
  def observe(self, callback: Callable[[str, Any], None]) -> None:
268
316
  """
jupyter_ydoc/yunicode.py CHANGED
@@ -63,6 +63,10 @@ class YUnicode(YBaseDoc):
63
63
  :param value: The content of the document.
64
64
  :type value: str
65
65
  """
66
+ if self.get() == value:
67
+ # no-op if the values are already the same,
68
+ # to avoid side-effects such as cursor jumping to the top
69
+ return
66
70
  with self._ydoc.transaction():
67
71
  # clear document
68
72
  self._ysource.clear()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jupyter-ydoc
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Document structures for collaborative editing using Ypy
5
5
  Project-URL: Homepage, https://jupyter.org
6
6
  Project-URL: Source, https://github.com/jupyter-server/jupyter_ydoc
@@ -23,7 +23,7 @@ Provides-Extra: test
23
23
  Requires-Dist: httpx-ws>=0.5.2; extra == 'test'
24
24
  Requires-Dist: hypercorn>=0.16.0; extra == 'test'
25
25
  Requires-Dist: pre-commit; extra == 'test'
26
- Requires-Dist: pycrdt-websocket<0.16.0,>=0.15.0; extra == 'test'
26
+ Requires-Dist: pycrdt-websocket<0.17.0,>=0.16.0; extra == 'test'
27
27
  Requires-Dist: pytest; extra == 'test'
28
28
  Requires-Dist: pytest-asyncio; extra == 'test'
29
29
  Description-Content-Type: text/markdown
@@ -1,14 +1,14 @@
1
1
  jupyter_ydoc/__init__.py,sha256=cVoElIvmvfj0IECe1gKe3f5vyyO7Dq7dMB_A0s2zjs4,649
2
- jupyter_ydoc/_version.py,sha256=GXnjthE_UvQKNieIwdSShRd-jmkzn3bxIk73dyfZ6DY,171
2
+ jupyter_ydoc/_version.py,sha256=vHwe_4Tu6OwXVP4fkl96hW7mdlnRCdb7oHGS6bAJ4Uk,171
3
3
  jupyter_ydoc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  jupyter_ydoc/utils.py,sha256=jnjF2uNmioh0z_4ymmMSYiB_2dAKSLmNFzVvR5V5Td0,883
5
5
  jupyter_ydoc/ybasedoc.py,sha256=RUkrlfu7xp6UfnxhKLB9tHCQyQDTAH10jmQ_AEY5Suo,5119
6
6
  jupyter_ydoc/yblob.py,sha256=44ZUakq9anNLAcaEuM9RpVN-J6Owf5kFpJJ80mTni8c,2253
7
7
  jupyter_ydoc/yfile.py,sha256=XTMtAXDWgIOLU2KUQxkLJz2cGvSPlOxpvJc4daXCV6I,198
8
- jupyter_ydoc/ynotebook.py,sha256=wbZiMgFMOg5xhBzLOfsFSeUiTlnXgV6yG-fsk161ZoE,8965
9
- jupyter_ydoc/yunicode.py,sha256=Um1BIGwne10ZQODeRA1qIi_vss-u3MKsjZNjbKodfQM,2385
10
- jupyter_ydoc-3.1.0.dist-info/METADATA,sha256=Wxoxi1bFx62bhtH18WaQzxjOQMa5lpv-nKw2Se4MWrw,3063
11
- jupyter_ydoc-3.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
- jupyter_ydoc-3.1.0.dist-info/entry_points.txt,sha256=lgvRG-rpsjRKf8cy7LpO7fqwwXy0sBVMCwhGOHgn4mc,164
13
- jupyter_ydoc-3.1.0.dist-info/licenses/LICENSE,sha256=dqphsFbhnlzPK7Vlkc66Zc7g7PS-e1dln07GXIVpFCQ,1567
14
- jupyter_ydoc-3.1.0.dist-info/RECORD,,
8
+ jupyter_ydoc/ynotebook.py,sha256=E_dN0oxd2Ic29JJKqXxHvm7Z8RVEHDVG_iiDB2aZz0U,10883
9
+ jupyter_ydoc/yunicode.py,sha256=s-ZuzSCMVViIWmBxC-gCw9oy3CmE2jT4WG40KW9X3xM,2562
10
+ jupyter_ydoc-3.2.0.dist-info/METADATA,sha256=lgoQrco4Gw9pTka96qcl2zCdcmIFoX8akVEJoJk1odQ,3063
11
+ jupyter_ydoc-3.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
+ jupyter_ydoc-3.2.0.dist-info/entry_points.txt,sha256=lgvRG-rpsjRKf8cy7LpO7fqwwXy0sBVMCwhGOHgn4mc,164
13
+ jupyter_ydoc-3.2.0.dist-info/licenses/LICENSE,sha256=dqphsFbhnlzPK7Vlkc66Zc7g7PS-e1dln07GXIVpFCQ,1567
14
+ jupyter_ydoc-3.2.0.dist-info/RECORD,,