jupyter-server-ydoc 2.0.2__tar.gz → 2.1.0b0__tar.gz

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.
Files changed (29) hide show
  1. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/PKG-INFO +1 -1
  2. jupyter_server_ydoc-2.1.0b0/jupyter_server_ydoc/_version.py +1 -0
  3. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/handlers.py +15 -0
  4. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/pytest_plugin.py +1 -1
  5. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/rooms.py +27 -1
  6. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/utils.py +1 -0
  7. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/tests/test_rooms.py +45 -3
  8. jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/_version.py +0 -1
  9. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/.gitignore +0 -0
  10. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/LICENSE +0 -0
  11. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/README.md +0 -0
  12. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter-config/jupyter_server_ydoc.json +0 -0
  13. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/__init__.py +0 -0
  14. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/app.py +0 -0
  15. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/events/awareness.yaml +0 -0
  16. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/events/fork.yaml +0 -0
  17. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/events/session.yaml +0 -0
  18. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/loaders.py +0 -0
  19. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/stores.py +0 -0
  20. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/test_utils.py +0 -0
  21. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/jupyter_server_ydoc/websocketserver.py +0 -0
  22. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/pyproject.toml +0 -0
  23. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/setup.py +0 -0
  24. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/tests/__init__.py +0 -0
  25. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/tests/conftest.py +0 -0
  26. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/tests/test_app.py +0 -0
  27. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/tests/test_documents.py +0 -0
  28. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/tests/test_handlers.py +0 -0
  29. {jupyter_server_ydoc-2.0.2 → jupyter_server_ydoc-2.1.0b0}/tests/test_loaders.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jupyter-server-ydoc
3
- Version: 2.0.2
3
+ Version: 2.1.0b0
4
4
  Summary: jupyter-server extension integrating collaborative shared models.
5
5
  Author-email: Jupyter Development Team <jupyter@googlegroups.com>
6
6
  License: # Licensing terms
@@ -0,0 +1 @@
1
+ __version__ = "2.1.0b0"
@@ -9,6 +9,7 @@ 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
@@ -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
 
@@ -291,6 +294,18 @@ 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
+ try:
303
+ room = cast(DocumentRoom, self.room)
304
+ room._save_to_disc()
305
+ except Exception:
306
+ self.log.error("Couldn't save content from room: %s", self._room_id)
307
+ return
308
+
294
309
  self._message_queue.put_nowait(message)
295
310
  self._websocket_server.ypatch_nb += 1
296
311
 
@@ -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,6 +247,33 @@ 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
 
@@ -265,7 +292,6 @@ class DocumentRoom(YRoom):
265
292
  """
266
293
  if self._save_delay is None:
267
294
  return
268
-
269
295
  if saving_document is not None and not saving_document.done():
270
296
  # the document is being saved, cancel that
271
297
  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
 
@@ -51,9 +51,7 @@ async def test_defined_save_delay_should_save_content_after_document_change(
51
51
  rtc_create_mock_document_room,
52
52
  ):
53
53
  content = "test"
54
- cm, _, room = rtc_create_mock_document_room(
55
- "test-id", "test.txt", content, save_delay=0.01, writable=True
56
- )
54
+ cm, _, room = rtc_create_mock_document_room("test-id", "test.txt", content, save_delay=0.01)
57
55
 
58
56
  await room.initialize()
59
57
  room._document.source = "Test 2"
@@ -79,6 +77,50 @@ async def test_undefined_save_delay_should_not_save_content_after_document_chang
79
77
  assert "save" not in cm.actions
80
78
 
81
79
 
80
+ async def test_should_not_save_content_when_all_clients_have_autosave_disabled(
81
+ rtc_create_mock_document_room,
82
+ ):
83
+ content = "test"
84
+ cm, _, room = rtc_create_mock_document_room("test-id", "test.txt", content, save_delay=0.01)
85
+
86
+ # Disable autosave for all existing clients
87
+ for state in room.awareness._states.values():
88
+ if state is not None:
89
+ state["autosave"] = False
90
+
91
+ # Inject a dummy client with autosave disabled
92
+ room.awareness._states[9999] = {"autosave": False}
93
+
94
+ await room.initialize()
95
+ room._document.source = "Test 2"
96
+
97
+ await asyncio.sleep(0.15)
98
+
99
+ assert "save" not in cm.actions
100
+
101
+
102
+ async def test_should_save_content_when_at_least_one_client_has_autosave_enabled(
103
+ rtc_create_mock_document_room,
104
+ ):
105
+ content = "test"
106
+ cm, _, room = rtc_create_mock_document_room("test-id", "test.txt", content, save_delay=0.01)
107
+
108
+ # Disable autosave for all existing clients
109
+ for state in room.awareness._states.values():
110
+ if state is not None:
111
+ state["autosave"] = False
112
+
113
+ # Inject a dummy client with autosave enabled
114
+ room.awareness._states[10000] = {"autosave": True}
115
+
116
+ await room.initialize()
117
+ room._document.source = "Test 2"
118
+
119
+ await asyncio.sleep(0.15)
120
+
121
+ assert "save" in cm.actions
122
+
123
+
82
124
  # The following test should be restored when package versions are fixed.
83
125
 
84
126
  # async def test_document_path(rtc_create_mock_document_room):
@@ -1 +0,0 @@
1
- __version__ = "2.0.2"