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.
- jupyter_server_ydoc/_version.py +1 -1
- jupyter_server_ydoc/handlers.py +39 -2
- jupyter_server_ydoc/pytest_plugin.py +1 -1
- jupyter_server_ydoc/rooms.py +28 -1
- jupyter_server_ydoc/utils.py +1 -0
- {jupyter_server_ydoc-2.0.2.dist-info → jupyter_server_ydoc-2.1.0.dist-info}/METADATA +6 -1
- {jupyter_server_ydoc-2.0.2.dist-info → jupyter_server_ydoc-2.1.0.dist-info}/RECORD +10 -10
- {jupyter_server_ydoc-2.0.2.data → jupyter_server_ydoc-2.1.0.data}/data/etc/jupyter/jupyter_server_config.d/jupyter_collaboration.json +0 -0
- {jupyter_server_ydoc-2.0.2.dist-info → jupyter_server_ydoc-2.1.0.dist-info}/WHEEL +0 -0
- {jupyter_server_ydoc-2.0.2.dist-info → jupyter_server_ydoc-2.1.0.dist-info}/licenses/LICENSE +0 -0
jupyter_server_ydoc/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.0
|
|
1
|
+
__version__ = "2.1.0"
|
jupyter_server_ydoc/handlers.py
CHANGED
|
@@ -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 =
|
|
279
|
+
writable: bool = True,
|
|
280
280
|
) -> tuple[FakeContentsManager, FileLoader, DocumentRoom]:
|
|
281
281
|
paths = {id: path}
|
|
282
282
|
|
jupyter_server_ydoc/rooms.py
CHANGED
|
@@ -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()
|
jupyter_server_ydoc/utils.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jupyter-server-ydoc
|
|
3
|
-
Version: 2.0
|
|
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=
|
|
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=
|
|
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=
|
|
7
|
-
jupyter_server_ydoc/rooms.py,sha256=
|
|
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=
|
|
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.
|
|
16
|
-
jupyter_server_ydoc-2.0.
|
|
17
|
-
jupyter_server_ydoc-2.0.
|
|
18
|
-
jupyter_server_ydoc-2.0.
|
|
19
|
-
jupyter_server_ydoc-2.0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
{jupyter_server_ydoc-2.0.2.dist-info → jupyter_server_ydoc-2.1.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|