jupyter-server-ydoc 2.1.0rc0__tar.gz → 2.1.2__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.1.0rc0 → jupyter_server_ydoc-2.1.2}/PKG-INFO +8 -3
  2. jupyter_server_ydoc-2.1.2/jupyter_server_ydoc/_version.py +1 -0
  3. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/app.py +1 -1
  4. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/handlers.py +28 -6
  5. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/pytest_plugin.py +4 -4
  6. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/rooms.py +3 -2
  7. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/stores.py +2 -2
  8. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/test_utils.py +0 -30
  9. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/websocketserver.py +4 -5
  10. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/pyproject.toml +9 -2
  11. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/tests/test_documents.py +3 -3
  12. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/tests/test_handlers.py +10 -11
  13. jupyter_server_ydoc-2.1.0rc0/jupyter_server_ydoc/_version.py +0 -1
  14. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/.gitignore +0 -0
  15. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/LICENSE +0 -0
  16. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/README.md +0 -0
  17. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter-config/jupyter_server_ydoc.json +0 -0
  18. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/__init__.py +0 -0
  19. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/events/awareness.yaml +0 -0
  20. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/events/fork.yaml +0 -0
  21. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/events/session.yaml +0 -0
  22. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/loaders.py +0 -0
  23. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/jupyter_server_ydoc/utils.py +0 -0
  24. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/setup.py +0 -0
  25. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/tests/__init__.py +0 -0
  26. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/tests/conftest.py +0 -0
  27. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/tests/test_app.py +0 -0
  28. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/tests/test_loaders.py +0 -0
  29. {jupyter_server_ydoc-2.1.0rc0 → jupyter_server_ydoc-2.1.2}/tests/test_rooms.py +0 -0
@@ -1,7 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jupyter-server-ydoc
3
- Version: 2.1.0rc0
3
+ Version: 2.1.2
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
 
@@ -79,9 +84,9 @@ Requires-Dist: jsonschema>=4.18.0
79
84
  Requires-Dist: jupyter-events>=0.11.0
80
85
  Requires-Dist: jupyter-server-fileid<1,>=0.7.0
81
86
  Requires-Dist: jupyter-server<3.0.0,>=2.15.0
82
- Requires-Dist: jupyter-ydoc!=3.0.0,!=3.0.1,<4.0.0,>=2.1.2
87
+ Requires-Dist: jupyter-ydoc<4.0.0,>=3.0.3
83
88
  Requires-Dist: pycrdt
84
- Requires-Dist: pycrdt-websocket<0.16.0,>=0.15.0
89
+ Requires-Dist: pycrdt-websocket<0.17.0,>=0.16.0
85
90
  Provides-Extra: test
86
91
  Requires-Dist: anyio; extra == 'test'
87
92
  Requires-Dist: coverage; extra == 'test'
@@ -0,0 +1 @@
1
+ __version__ = "2.1.2"
@@ -10,7 +10,7 @@ from jupyter_server.extension.application import ExtensionApp
10
10
  from jupyter_ydoc import ydocs as YDOCS
11
11
  from jupyter_ydoc.ybasedoc import YBaseDoc
12
12
  from pycrdt import Doc
13
- from pycrdt_websocket.ystore import BaseYStore
13
+ from pycrdt.store import BaseYStore
14
14
  from traitlets import Bool, Float, Type
15
15
 
16
16
  from .handlers import (
@@ -15,9 +15,9 @@ from jupyter_server.auth import authorized
15
15
  from jupyter_server.base.handlers import APIHandler, JupyterHandler
16
16
  from jupyter_server.utils import ensure_async
17
17
  from jupyter_ydoc import ydocs as YDOCS
18
- from pycrdt import Doc, UndoManager
19
- from pycrdt_websocket.yroom import YRoom
20
- from pycrdt_websocket.ystore import BaseYStore
18
+ from pycrdt import Doc, Encoder, UndoManager
19
+ from pycrdt.store import BaseYStore
20
+ from pycrdt.websocket import YRoom
21
21
  from tornado import web
22
22
  from tornado.websocket import WebSocketHandler
23
23
 
@@ -120,7 +120,7 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
120
120
  updates_file_path = f".{file_type}:{file_id}.y"
121
121
  ystore = self._ystore_class(
122
122
  path=updates_file_path,
123
- log=self.log, # type:ignore[call-arg]
123
+ log=self.log,
124
124
  )
125
125
  self.room = DocumentRoom(
126
126
  self._room_id,
@@ -273,7 +273,7 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
273
273
  if self._room_id != "JupyterLab:globalAwareness":
274
274
  self._emit_awareness_event(self.current_user.username, "join")
275
275
 
276
- async def send(self, message):
276
+ async def send(self, message: bytes) -> None:
277
277
  """
278
278
  Send a message to the client.
279
279
  """
@@ -299,16 +299,38 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
299
299
  if header == MessageType.RAW:
300
300
  msg = decoder.read_var_string()
301
301
  if msg == "save":
302
+ save_id = decoder.read_var_uint()
303
+ save_reply = {
304
+ "type": "save",
305
+ "responseTo": save_id,
306
+ }
302
307
  try:
303
308
  room = cast(DocumentRoom, self.room)
304
- room._save_to_disc()
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
+ )
305
320
  except Exception:
306
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"}))
307
323
  return
308
324
 
309
325
  self._message_queue.put_nowait(message)
310
326
  self._websocket_server.ypatch_nb += 1
311
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
+
312
334
  def on_close(self) -> None:
313
335
  """
314
336
  On connection close.
@@ -14,13 +14,13 @@ from jupyter_server_ydoc.loaders import FileLoader
14
14
  from jupyter_server_ydoc.rooms import DocumentRoom
15
15
  from jupyter_server_ydoc.stores import SQLiteYStore
16
16
  from jupyter_ydoc import YNotebook, YUnicode
17
- from pycrdt_websocket import WebsocketProvider
17
+ from pycrdt import Provider
18
+ from pycrdt.websocket.websocket import HttpxWebsocket
18
19
 
19
20
  from .test_utils import (
20
21
  FakeContentsManager,
21
22
  FakeEventLogger,
22
23
  FakeFileIDManager,
23
- Websocket,
24
24
  )
25
25
 
26
26
 
@@ -231,7 +231,7 @@ def rtc_add_doc_to_store(rtc_connect_doc_client):
231
231
  doc.observe(_on_document_change)
232
232
 
233
233
  websocket, room_name = await rtc_connect_doc_client(format, type, path)
234
- async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws, room_name)):
234
+ async with websocket as ws, Provider(doc.ydoc, HttpxWebsocket(ws, room_name)):
235
235
  await event.wait()
236
236
  await sleep(0.1)
237
237
 
@@ -243,7 +243,7 @@ def rtc_create_SQLite_store_factory(jp_serverapp):
243
243
  db = SQLiteYStore(
244
244
  path=f"{type}:{path}",
245
245
  # `SQLiteYStore` here is a subclass of booth `LoggingConfigurable`
246
- # and `pycrdt_websocket.ystore.SQLiteYStore`, but mypy gets lost:
246
+ # and `pycrdt.store.SQLiteYStore`, but mypy gets lost:
247
247
  config=jp_serverapp.config, # type:ignore[call-arg]
248
248
  )
249
249
  _ = create_task(db.start())
@@ -9,8 +9,8 @@ from typing import Any, Callable
9
9
 
10
10
  from jupyter_events import EventLogger
11
11
  from jupyter_ydoc import ydocs as YDOCS
12
- from pycrdt_websocket.yroom import YRoom
13
- from pycrdt_websocket.ystore import BaseYStore, YDocNotFound
12
+ from pycrdt.websocket import YRoom
13
+ from pycrdt.store import BaseYStore, YDocNotFound
14
14
 
15
15
  from .loaders import FileLoader
16
16
  from .utils import JUPYTER_COLLABORATION_EVENTS_URI, LogLevel, OutOfBandChanges
@@ -280,6 +280,7 @@ class DocumentRoom(YRoom):
280
280
  self._saving_document = asyncio.create_task(
281
281
  self._maybe_save_document(self._saving_document)
282
282
  )
283
+ return self._saving_document
283
284
 
284
285
  async def _maybe_save_document(self, saving_document: asyncio.Task | None) -> None:
285
286
  """
@@ -1,8 +1,8 @@
1
1
  # Copyright (c) Jupyter Development Team.
2
2
  # Distributed under the terms of the Modified BSD License.
3
3
 
4
- from pycrdt_websocket.ystore import SQLiteYStore as _SQLiteYStore
5
- from pycrdt_websocket.ystore import TempFileYStore as _TempFileYStore
4
+ from pycrdt.store import SQLiteYStore as _SQLiteYStore
5
+ from pycrdt.store import TempFileYStore as _TempFileYStore
6
6
  from traitlets import Int, Unicode
7
7
  from traitlets.config import LoggingConfigurable
8
8
 
@@ -6,7 +6,6 @@ from __future__ import annotations
6
6
  from datetime import datetime
7
7
  from typing import Any
8
8
 
9
- from anyio import Lock
10
9
  from jupyter_server import _tz as tz
11
10
 
12
11
 
@@ -56,32 +55,3 @@ class FakeContentsManager:
56
55
  class FakeEventLogger:
57
56
  def emit(self, schema_id: str, data: dict) -> None:
58
57
  print(data)
59
-
60
-
61
- class Websocket:
62
- def __init__(self, websocket: Any, path: str):
63
- self._websocket = websocket
64
- self._path = path
65
- self._send_lock = Lock()
66
-
67
- @property
68
- def path(self) -> str:
69
- return self._path
70
-
71
- def __aiter__(self):
72
- return self
73
-
74
- async def __anext__(self) -> bytes:
75
- try:
76
- message = await self.recv()
77
- except Exception:
78
- raise StopAsyncIteration()
79
- return message
80
-
81
- async def send(self, message: bytes) -> None:
82
- async with self._send_lock:
83
- await self._websocket.send_bytes(message)
84
-
85
- async def recv(self) -> bytes:
86
- b = await self._websocket.receive_bytes()
87
- return bytes(b)
@@ -7,10 +7,9 @@ import asyncio
7
7
  from logging import Logger
8
8
  from typing import Any, Callable
9
9
 
10
- from pycrdt_websocket.websocket import Websocket
11
- from pycrdt_websocket.websocket_server import WebsocketServer
12
- from pycrdt_websocket.yroom import YRoom
13
- from pycrdt_websocket.ystore import BaseYStore
10
+ from pycrdt import Channel
11
+ from pycrdt.store import BaseYStore
12
+ from pycrdt.websocket import WebsocketServer, YRoom
14
13
 
15
14
 
16
15
  class RoomNotFound(LookupError):
@@ -133,7 +132,7 @@ class JupyterWebsocketServer(WebsocketServer):
133
132
  await self.start_room(room)
134
133
  return room
135
134
 
136
- async def serve(self, websocket: Websocket) -> None:
135
+ async def serve(self, websocket: Channel) -> None:
137
136
  # start monitoring here as the event loop is not yet available when initializing the object
138
137
  if self.monitor_task is None:
139
138
  self.monitor_task = asyncio.create_task(self._monitor())
@@ -29,15 +29,22 @@ authors = [
29
29
  ]
30
30
  dependencies = [
31
31
  "jupyter_server>=2.15.0,<3.0.0",
32
- "jupyter_ydoc>=2.1.2,<4.0.0,!=3.0.0,!=3.0.1",
32
+ "jupyter_ydoc>=3.0.3,<4.0.0",
33
33
  "pycrdt",
34
- "pycrdt-websocket>=0.15.0,<0.16.0",
34
+ "pycrdt-websocket>=0.16.0,<0.17.0",
35
35
  "jupyter_events>=0.11.0",
36
36
  "jupyter_server_fileid>=0.7.0,<1",
37
37
  "jsonschema>=4.18.0"
38
38
  ]
39
39
  dynamic = ["version"]
40
40
 
41
+ [project.urls]
42
+ Documentation = "https://jupyterlab-realtime-collaboration.readthedocs.io/"
43
+ Repository = "https://github.com/jupyterlab/jupyter-collaboration"
44
+ Changelog = "https://jupyterlab-realtime-collaboration.readthedocs.io/en/latest/changelog.html"
45
+ Source = "https://github.com/jupyterlab/jupyter-collaboration/tree/main/projects/jupyter-server-ydoc"
46
+ Issues = "https://github.com/jupyterlab/jupyter-collaboration/issues/new/choose"
47
+
41
48
  [project.optional-dependencies]
42
49
  test = [
43
50
  "coverage",
@@ -11,8 +11,8 @@ else:
11
11
 
12
12
  import pytest
13
13
  from anyio import create_task_group, sleep
14
- from jupyter_server_ydoc.test_utils import Websocket
15
- from pycrdt_websocket import WebsocketProvider
14
+ from pycrdt import Provider
15
+ from pycrdt.websocket.websocket import HttpxWebsocket
16
16
 
17
17
  jupyter_ydocs = {ep.name: ep.load() for ep in entry_points(group="jupyter_ydoc")}
18
18
 
@@ -34,7 +34,7 @@ async def test_dirty(
34
34
  jupyter_ydoc = jupyter_ydocs[file_type]()
35
35
 
36
36
  websocket, room_name = await rtc_connect_doc_client(file_format, file_type, file_path)
37
- async with websocket as ws, WebsocketProvider(jupyter_ydoc.ydoc, Websocket(ws, room_name)):
37
+ async with websocket as ws, Provider(jupyter_ydoc.ydoc, HttpxWebsocket(ws, room_name)):
38
38
  for _ in range(2):
39
39
  jupyter_ydoc.dirty = True
40
40
  await sleep(rtc_document_save_delay * 1.5)
@@ -9,10 +9,9 @@ from typing import Any
9
9
 
10
10
  from dirty_equals import IsStr
11
11
  from jupyter_events.logger import EventLogger
12
- from jupyter_server_ydoc.test_utils import Websocket
13
12
  from jupyter_ydoc import YUnicode
14
- from pycrdt import Text
15
- from pycrdt_websocket import WebsocketProvider
13
+ from pycrdt import Text, Provider
14
+ from pycrdt.websocket.websocket import HttpxWebsocket
16
15
 
17
16
 
18
17
  async def test_session_handler_should_create_session_id(
@@ -81,7 +80,7 @@ async def test_room_handler_doc_client_should_connect(rtc_create_file, rtc_conne
81
80
  doc.observe(_on_document_change)
82
81
 
83
82
  websocket, room_name = await rtc_connect_doc_client("text", "file", path)
84
- async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws, room_name)):
83
+ async with websocket as ws, Provider(doc.ydoc, HttpxWebsocket(ws, room_name)):
85
84
  await event.wait()
86
85
  await sleep(0.1)
87
86
 
@@ -117,7 +116,7 @@ async def test_room_handler_doc_client_should_emit_awareness_event(
117
116
  )
118
117
 
119
118
  websocket, room_name = await rtc_connect_doc_client("text", "file", path)
120
- async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws, room_name)):
119
+ async with websocket as ws, Provider(doc.ydoc, HttpxWebsocket(ws, room_name)):
121
120
  await event.wait()
122
121
  await sleep(0.1)
123
122
 
@@ -149,7 +148,7 @@ async def test_room_handler_doc_client_should_cleanup_room_file(
149
148
  doc.observe(_on_document_change)
150
149
 
151
150
  websocket, room_name = await rtc_connect_doc_client("text", "file", path)
152
- async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws, room_name)):
151
+ async with websocket as ws, Provider(doc.ydoc, HttpxWebsocket(ws, room_name)):
153
152
  await event.wait()
154
153
  await sleep(0.1)
155
154
 
@@ -174,7 +173,7 @@ async def test_room_handler_doc_client_should_cleanup_room_file(
174
173
 
175
174
  try:
176
175
  websocket, room_name = await rtc_connect_doc_client("text2", "file2", path2)
177
- async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws, room_name)):
176
+ async with websocket as ws, Provider(doc.ydoc, HttpxWebsocket(ws, room_name)):
178
177
  await event.wait()
179
178
  await sleep(0.1)
180
179
  except Exception:
@@ -182,7 +181,7 @@ async def test_room_handler_doc_client_should_cleanup_room_file(
182
181
 
183
182
  try:
184
183
  websocket, room_name = await rtc_connect_doc_client("text2", "file2", path2)
185
- async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws, room_name)):
184
+ async with websocket as ws, Provider(doc.ydoc, HttpxWebsocket(ws, room_name)):
186
185
  await event.wait()
187
186
  await sleep(0.1)
188
187
  except Exception:
@@ -253,7 +252,7 @@ async def test_fork_handler(
253
252
  root_roomid = f"text:file:{file_id}"
254
253
 
255
254
  websocket, room_name = await rtc_connect_doc_client("text", "file", path)
256
- async with websocket as ws, WebsocketProvider(root_ydoc.ydoc, Websocket(ws, room_name)):
255
+ async with websocket as ws, Provider(root_ydoc.ydoc, HttpxWebsocket(ws, room_name)):
257
256
  await root_connect_event.wait()
258
257
 
259
258
  resp = await rtc_create_fork_client(root_roomid, False, "my fork0", "is awesome0")
@@ -316,8 +315,8 @@ async def test_fork_handler(
316
315
  fork_ydoc.observe(_on_fork_change)
317
316
  fork_text = fork_ydoc.ydoc.get("source", type=Text)
318
317
 
319
- async with await rtc_connect_fork_client(fork_roomid1) as ws, WebsocketProvider(
320
- fork_ydoc.ydoc, Websocket(ws, fork_roomid1)
318
+ async with await rtc_connect_fork_client(fork_roomid1) as ws, Provider(
319
+ fork_ydoc.ydoc, HttpxWebsocket(ws, fork_roomid1)
321
320
  ):
322
321
  await fork_connect_event.wait()
323
322
  root_text = root_ydoc.ydoc.get("source", type=Text)
@@ -1 +0,0 @@
1
- __version__ = "2.1.0rc0"