jupyter-server-ydoc 2.0.2__py3-none-any.whl → 2.1.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.
@@ -1 +1 @@
1
- __version__ = "2.0.2"
1
+ __version__ = "2.1.0"
@@ -9,12 +9,13 @@ import uuid
9
9
  from logging import Logger
10
10
  from typing import Any
11
11
  from uuid import uuid4
12
+ from typing import cast
12
13
 
13
14
  from jupyter_server.auth import authorized
14
15
  from jupyter_server.base.handlers import APIHandler, JupyterHandler
15
16
  from jupyter_server.utils import ensure_async
16
17
  from jupyter_ydoc import ydocs as YDOCS
17
- from pycrdt import Doc, UndoManager
18
+ from pycrdt import Doc, Encoder, UndoManager
18
19
  from pycrdt_websocket.yroom import YRoom
19
20
  from pycrdt_websocket.ystore import BaseYStore
20
21
  from tornado import web
@@ -32,6 +33,8 @@ from .utils import (
32
33
  room_id_from_encoded_path,
33
34
  )
34
35
  from .websocketserver import JupyterWebsocketServer, RoomNotFound
36
+ from .utils import MessageType
37
+ from pycrdt import Decoder
35
38
 
36
39
  YFILE = YDOCS["file"]
37
40
 
@@ -270,7 +273,7 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
270
273
  if self._room_id != "JupyterLab:globalAwareness":
271
274
  self._emit_awareness_event(self.current_user.username, "join")
272
275
 
273
- async def send(self, message):
276
+ async def send(self, message: bytes) -> None:
274
277
  """
275
278
  Send a message to the client.
276
279
  """
@@ -291,9 +294,43 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
291
294
  """
292
295
  On message receive.
293
296
  """
297
+ decoder = Decoder(message)
298
+ header = decoder.read_var_uint()
299
+ if header == MessageType.RAW:
300
+ msg = decoder.read_var_string()
301
+ if msg == "save":
302
+ save_id = decoder.read_var_uint()
303
+ save_reply = {
304
+ "type": "save",
305
+ "responseTo": save_id,
306
+ }
307
+ try:
308
+ room = cast(DocumentRoom, self.room)
309
+ save_task = room._save_to_disc()
310
+ # task may be missing if save was already in progress
311
+ if save_task:
312
+ await save_task
313
+ await self.send(
314
+ self._encode_json_message({**save_reply, "status": "success"})
315
+ )
316
+ else:
317
+ await self.send(
318
+ self._encode_json_message({**save_reply, "status": "skipped"})
319
+ )
320
+ except Exception:
321
+ self.log.error("Couldn't save content from room: %s", self._room_id)
322
+ await self.send(self._encode_json_message({**save_reply, "status": "failed"}))
323
+ return
324
+
294
325
  self._message_queue.put_nowait(message)
295
326
  self._websocket_server.ypatch_nb += 1
296
327
 
328
+ def _encode_json_message(self, message: dict) -> bytes:
329
+ encoder = Encoder()
330
+ encoder.write_var_uint(MessageType.RAW)
331
+ encoder.write_var_string(json.dumps(message))
332
+ return encoder.to_bytes()
333
+
297
334
  def on_close(self) -> None:
298
335
  """
299
336
  On connection close.
@@ -276,7 +276,7 @@ def rtc_create_mock_document_room():
276
276
  last_modified: datetime | None = None,
277
277
  save_delay: float | None = None,
278
278
  store: SQLiteYStore | None = None,
279
- writable: bool = False,
279
+ writable: bool = True,
280
280
  ) -> tuple[FakeContentsManager, FileLoader, DocumentRoom]:
281
281
  paths = {id: path}
282
282
 
@@ -247,12 +247,40 @@ class DocumentRoom(YRoom):
247
247
  document. This tasks are debounced (60 seconds by default) so we
248
248
  need to cancel previous tasks before creating a new one.
249
249
  """
250
+ # Collect autosave values from all clients
251
+ autosave_states = [
252
+ state.get("autosave", True)
253
+ for state in self.awareness.states.values()
254
+ if state # skip empty states
255
+ ]
256
+
257
+ # If no states exist (e.g., during tests), force autosave to be True
258
+ if not autosave_states:
259
+ autosave_states = [True]
260
+
261
+ # Enable autosave if at least one client has it turned on
262
+ autosave = any(autosave_states)
263
+
264
+ if not autosave:
265
+ return
266
+ if self._update_lock.locked():
267
+ return
268
+
269
+ self._saving_document = asyncio.create_task(
270
+ self._maybe_save_document(self._saving_document)
271
+ )
272
+
273
+ def _save_to_disc(self):
274
+ """
275
+ Called when manual save is triggered. Helpful when autosave is turned off.
276
+ """
250
277
  if self._update_lock.locked():
251
278
  return
252
279
 
253
280
  self._saving_document = asyncio.create_task(
254
281
  self._maybe_save_document(self._saving_document)
255
282
  )
283
+ return self._saving_document
256
284
 
257
285
  async def _maybe_save_document(self, saving_document: asyncio.Task | None) -> None:
258
286
  """
@@ -265,7 +293,6 @@ class DocumentRoom(YRoom):
265
293
  """
266
294
  if self._save_delay is None:
267
295
  return
268
-
269
296
  if saving_document is not None and not saving_document.done():
270
297
  # the document is being saved, cancel that
271
298
  saving_document.cancel()
@@ -19,6 +19,7 @@ FORK_EVENTS_SCHEMA_PATH = EVENTS_FOLDER_PATH / "fork.yaml"
19
19
  class MessageType(IntEnum):
20
20
  SYNC = 0
21
21
  AWARENESS = 1
22
+ RAW = 2
22
23
  CHAT = 125
23
24
 
24
25
 
@@ -1,7 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jupyter-server-ydoc
3
- Version: 2.0.2
3
+ Version: 2.1.0
4
4
  Summary: jupyter-server extension integrating collaborative shared models.
5
+ Project-URL: Documentation, https://jupyterlab-realtime-collaboration.readthedocs.io/
6
+ Project-URL: Repository, https://github.com/jupyterlab/jupyter-collaboration
7
+ Project-URL: Changelog, https://jupyterlab-realtime-collaboration.readthedocs.io/en/latest/changelog.html
8
+ Project-URL: Source, https://github.com/jupyterlab/jupyter-collaboration/tree/main/projects/jupyter-server-ydoc
9
+ Project-URL: Issues, https://github.com/jupyterlab/jupyter-collaboration/issues/new/choose
5
10
  Author-email: Jupyter Development Team <jupyter@googlegroups.com>
6
11
  License: # Licensing terms
7
12
 
@@ -1,19 +1,19 @@
1
1
  jupyter_server_ydoc/__init__.py,sha256=B8H7XLhzgrTCQD8304Lx91FYXslwabsnV9OuYu4M4Hw,346
2
- jupyter_server_ydoc/_version.py,sha256=tATvJM5shAzfspHYjdVwpV2w3-gDA119NlEYi5X2lFY,22
2
+ jupyter_server_ydoc/_version.py,sha256=Xybt2skBZamGMNlLuOX1IG-h4uIxqUDGAO8MIGWrJac,22
3
3
  jupyter_server_ydoc/app.py,sha256=6Zca0acaEFCKyUbXdmcPmVk1Dgu91Y-DFjYR16WKFlg,8161
4
- jupyter_server_ydoc/handlers.py,sha256=Prsi_IlJa9wl9VszRr-2qKZ8z224u-o7lRYI5SNJBaE,27276
4
+ jupyter_server_ydoc/handlers.py,sha256=x3R5VluXWFN460AiC7xNbipSpCR7SQSjqrcwjBorgpw,28836
5
5
  jupyter_server_ydoc/loaders.py,sha256=XUQqg2EbfQUYlQVjHY183gYKeVZ6x92VHy4EsOQz4fA,11303
6
- jupyter_server_ydoc/pytest_plugin.py,sha256=MfFqL5xJLGeIuLG8GF1kr2TumRjIIQfxoeNLGjvQjY8,8742
7
- jupyter_server_ydoc/rooms.py,sha256=u4umGLsudteR6oJvw4pcszzrZTDgzOZkVXZEOfBPk8M,12221
6
+ jupyter_server_ydoc/pytest_plugin.py,sha256=yMVlk7fvHXiM7g6ua0Ophc96cFwC0bmTWVDbbUeu2DE,8741
7
+ jupyter_server_ydoc/rooms.py,sha256=O0dYKeDRMUPFRraFJf8irqnFaSD80hirrtAYBXKTgrM,13103
8
8
  jupyter_server_ydoc/stores.py,sha256=_5J6eNs3R5Tv88PCc-GGuszxQstfvNoBCYABqzBzJXA,1004
9
9
  jupyter_server_ydoc/test_utils.py,sha256=utUwB5FThc_SCQshhUbLNih9GUa5qBcmMgU6-jx0ZnA,2275
10
- jupyter_server_ydoc/utils.py,sha256=EgKC15js8VOS8-5jGMs4pfHQfV9drnNT2Gew5UlyXZc,2171
10
+ jupyter_server_ydoc/utils.py,sha256=_wI4CFOEZK4MSMYCZe2moSbggUTRdGxY4qTzSDEFzdE,2183
11
11
  jupyter_server_ydoc/websocketserver.py,sha256=h1yTgJcsCK17_97Ne5x-lbgIFsxylwnltxagcuAlTJY,5185
12
12
  jupyter_server_ydoc/events/awareness.yaml,sha256=2FrCci5rZIaU4rn8pIPZJkd132YAZdzKjSNSwjOY7Dk,755
13
13
  jupyter_server_ydoc/events/fork.yaml,sha256=3OrhQjhVyLjlBJWMiffbnZodL3GzFafLwEmSBFrK33o,1303
14
14
  jupyter_server_ydoc/events/session.yaml,sha256=PS0MxowpRwY5QFYm-LJvHUxKHnsictV8_6VEwfhYxcQ,1596
15
- jupyter_server_ydoc-2.0.2.data/data/etc/jupyter/jupyter_server_config.d/jupyter_collaboration.json,sha256=0thh2hJUxAKkZSmneJMG0U6QJRjdM6zGlwrTedEt-Jk,94
16
- jupyter_server_ydoc-2.0.2.dist-info/METADATA,sha256=Wcc5iSM2ZdOp74R__1pRnFDoPYvNK0lm-y9WkQJjBX0,5115
17
- jupyter_server_ydoc-2.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- jupyter_server_ydoc-2.0.2.dist-info/licenses/LICENSE,sha256=mhO0ZW9EiWOPg0dUgB-lNbJ0CGwRmTdbeAg_se1SOnY,2833
19
- jupyter_server_ydoc-2.0.2.dist-info/RECORD,,
15
+ jupyter_server_ydoc-2.1.0.data/data/etc/jupyter/jupyter_server_config.d/jupyter_collaboration.json,sha256=0thh2hJUxAKkZSmneJMG0U6QJRjdM6zGlwrTedEt-Jk,94
16
+ jupyter_server_ydoc-2.1.0.dist-info/METADATA,sha256=Rfb1XEPiOQTYrI0JZ8ZFpiYvWIhmxCalR0-OmFPVwT8,5587
17
+ jupyter_server_ydoc-2.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ jupyter_server_ydoc-2.1.0.dist-info/licenses/LICENSE,sha256=mhO0ZW9EiWOPg0dUgB-lNbJ0CGwRmTdbeAg_se1SOnY,2833
19
+ jupyter_server_ydoc-2.1.0.dist-info/RECORD,,