jupyter-server-ydoc 2.0.1__tar.gz → 2.1.0a0__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.
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/PKG-INFO +1 -1
- jupyter_server_ydoc-2.1.0a0/jupyter_server_ydoc/_version.py +1 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/app.py +2 -2
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/handlers.py +31 -37
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/loaders.py +16 -1
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/pytest_plugin.py +7 -2
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/rooms.py +32 -6
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/utils.py +1 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/websocketserver.py +5 -4
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/tests/test_rooms.py +45 -3
- jupyter_server_ydoc-2.0.1/jupyter_server_ydoc/_version.py +0 -1
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/.gitignore +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/LICENSE +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/README.md +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter-config/jupyter_server_ydoc.json +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/__init__.py +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/events/awareness.yaml +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/events/fork.yaml +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/events/session.yaml +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/stores.py +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/test_utils.py +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/pyproject.toml +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/setup.py +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/tests/__init__.py +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/tests/conftest.py +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/tests/test_app.py +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/tests/test_documents.py +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/tests/test_handlers.py +0 -0
- {jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/tests/test_loaders.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.1.0.a0"
|
|
@@ -105,7 +105,7 @@ class YDocExtension(ExtensionApp):
|
|
|
105
105
|
page_config.setdefault("serverSideExecution", self.server_side_execution)
|
|
106
106
|
|
|
107
107
|
# Set configurable parameters to YStore class
|
|
108
|
-
ystore_class = partial(self.ystore_class, config=self.config)
|
|
108
|
+
ystore_class: type[BaseYStore] = partial(self.ystore_class, config=self.config) # type:ignore[assignment]
|
|
109
109
|
|
|
110
110
|
self.ywebsocket_server = JupyterWebsocketServer(
|
|
111
111
|
rooms_ready=False,
|
|
@@ -205,7 +205,7 @@ class YDocExtension(ExtensionApp):
|
|
|
205
205
|
if copy:
|
|
206
206
|
update = room.ydoc.get_update()
|
|
207
207
|
|
|
208
|
-
fork_ydoc = Doc()
|
|
208
|
+
fork_ydoc: Doc = Doc()
|
|
209
209
|
fork_ydoc.apply_update(update)
|
|
210
210
|
|
|
211
211
|
return YDOCS.get(room.file_type, YDOCS["file"])(fork_ydoc)
|
|
@@ -5,18 +5,18 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
7
|
import json
|
|
8
|
-
import time
|
|
9
8
|
import uuid
|
|
10
9
|
from logging import Logger
|
|
11
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
12
11
|
from uuid import uuid4
|
|
12
|
+
from typing import cast
|
|
13
13
|
|
|
14
14
|
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.
|
|
18
|
+
from pycrdt import Doc, UndoManager
|
|
19
|
+
from pycrdt_websocket.yroom import YRoom
|
|
20
20
|
from pycrdt_websocket.ystore import BaseYStore
|
|
21
21
|
from tornado import web
|
|
22
22
|
from tornado.websocket import WebSocketHandler
|
|
@@ -28,12 +28,13 @@ from .utils import (
|
|
|
28
28
|
JUPYTER_COLLABORATION_EVENTS_URI,
|
|
29
29
|
JUPYTER_COLLABORATION_FORK_EVENTS_URI,
|
|
30
30
|
LogLevel,
|
|
31
|
-
MessageType,
|
|
32
31
|
decode_file_path,
|
|
33
32
|
encode_file_path,
|
|
34
33
|
room_id_from_encoded_path,
|
|
35
34
|
)
|
|
36
35
|
from .websocketserver import JupyterWebsocketServer, RoomNotFound
|
|
36
|
+
from .utils import MessageType
|
|
37
|
+
from pycrdt import Decoder
|
|
37
38
|
|
|
38
39
|
YFILE = YDOCS["file"]
|
|
39
40
|
|
|
@@ -117,7 +118,10 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
|
|
|
117
118
|
|
|
118
119
|
file = self._file_loaders[file_id]
|
|
119
120
|
updates_file_path = f".{file_type}:{file_id}.y"
|
|
120
|
-
ystore = self._ystore_class(
|
|
121
|
+
ystore = self._ystore_class(
|
|
122
|
+
path=updates_file_path,
|
|
123
|
+
log=self.log, # type:ignore[call-arg]
|
|
124
|
+
)
|
|
121
125
|
self.room = DocumentRoom(
|
|
122
126
|
self._room_id,
|
|
123
127
|
file_format,
|
|
@@ -182,7 +186,7 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
|
|
|
182
186
|
self._websocket_server = ywebsocket_server
|
|
183
187
|
self._message_queue = asyncio.Queue()
|
|
184
188
|
self._room_id = ""
|
|
185
|
-
self.room = None
|
|
189
|
+
self.room = None # type:ignore
|
|
186
190
|
|
|
187
191
|
@property
|
|
188
192
|
def path(self):
|
|
@@ -219,7 +223,7 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
|
|
|
219
223
|
raise web.HTTPError(403)
|
|
220
224
|
return await super().get(*args, **kwargs)
|
|
221
225
|
|
|
222
|
-
async def open(self, room_id):
|
|
226
|
+
async def open(self, room_id: str) -> None: # type:ignore[override]
|
|
223
227
|
"""
|
|
224
228
|
On connection open.
|
|
225
229
|
"""
|
|
@@ -259,7 +263,7 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
|
|
|
259
263
|
)
|
|
260
264
|
|
|
261
265
|
# Clean up the room and delete the file loader
|
|
262
|
-
if len(self.room.clients) == 0 or self.room.clients ==
|
|
266
|
+
if len(self.room.clients) == 0 or self.room.clients == {self}:
|
|
263
267
|
self._message_queue.put_nowait(b"")
|
|
264
268
|
self._cleanup_delay = 0
|
|
265
269
|
await self._clean_room()
|
|
@@ -290,27 +294,17 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
|
|
|
290
294
|
"""
|
|
291
295
|
On message receive.
|
|
292
296
|
"""
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if
|
|
296
|
-
msg =
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
"
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
).encode("utf8")
|
|
306
|
-
|
|
307
|
-
for client in self.room.clients:
|
|
308
|
-
if client != self:
|
|
309
|
-
task = asyncio.create_task(
|
|
310
|
-
client.send(bytes([MessageType.CHAT]) + write_var_uint(len(data)) + data)
|
|
311
|
-
)
|
|
312
|
-
self._websocket_server.background_tasks.add(task)
|
|
313
|
-
task.add_done_callback(self._websocket_server.background_tasks.discard)
|
|
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
|
|
314
308
|
|
|
315
309
|
self._message_queue.put_nowait(message)
|
|
316
310
|
self._websocket_server.ypatch_nb += 1
|
|
@@ -321,7 +315,7 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
|
|
|
321
315
|
"""
|
|
322
316
|
# stop serving this client
|
|
323
317
|
self._message_queue.put_nowait(b"")
|
|
324
|
-
if isinstance(self.room, DocumentRoom) and self.room.clients ==
|
|
318
|
+
if isinstance(self.room, DocumentRoom) and self.room.clients == {self}:
|
|
325
319
|
# no client in this room after we disconnect
|
|
326
320
|
# keep the document for a while in case someone reconnects
|
|
327
321
|
self.log.info("Cleaning room: %s", self._room_id)
|
|
@@ -386,9 +380,7 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
|
|
|
386
380
|
self._emit(LogLevel.INFO, "clean", "Loader deleted.")
|
|
387
381
|
del self._room_locks[self._room_id]
|
|
388
382
|
|
|
389
|
-
def _on_global_awareness_event(
|
|
390
|
-
self, topic: Literal["change", "update"], changes: tuple[dict[str, Any], Any]
|
|
391
|
-
) -> None:
|
|
383
|
+
def _on_global_awareness_event(self, topic: str, changes: tuple[dict[str, Any], Any]) -> None:
|
|
392
384
|
"""
|
|
393
385
|
Update the users when the global awareness changes.
|
|
394
386
|
|
|
@@ -489,7 +481,7 @@ class TimelineHandler(APIHandler):
|
|
|
489
481
|
try:
|
|
490
482
|
room_id = room_id_from_encoded_path(encoded_path)
|
|
491
483
|
room: YRoom = await self.ywebsocket_server.get_room(room_id)
|
|
492
|
-
fork_ydoc = Doc()
|
|
484
|
+
fork_ydoc: Doc = Doc()
|
|
493
485
|
|
|
494
486
|
ydoc_factory = YDOCS.get(content_type)
|
|
495
487
|
if ydoc_factory is None:
|
|
@@ -505,7 +497,9 @@ class TimelineHandler(APIHandler):
|
|
|
505
497
|
FORK_DOCUMENTS[idx] = ydoc_factory(fork_ydoc)
|
|
506
498
|
undo_manager: UndoManager = FORK_DOCUMENTS[idx].undo_manager
|
|
507
499
|
|
|
508
|
-
|
|
500
|
+
ystore = room.ystore
|
|
501
|
+
assert ystore
|
|
502
|
+
updates_and_timestamps = [(item[0], item[-1]) async for item in ystore.read()]
|
|
509
503
|
|
|
510
504
|
result_timestamps = []
|
|
511
505
|
|
|
@@ -649,7 +643,7 @@ class DocForkHandler(APIHandler):
|
|
|
649
643
|
return self.finish({"code": 404, "error": "Root room not found"})
|
|
650
644
|
|
|
651
645
|
update = root_room.ydoc.get_update()
|
|
652
|
-
fork_ydoc = Doc()
|
|
646
|
+
fork_ydoc: Doc = Doc()
|
|
653
647
|
fork_ydoc.apply_update(update)
|
|
654
648
|
model = self.get_json_body()
|
|
655
649
|
synchronize = model.get("synchronize", False)
|
|
@@ -203,13 +203,28 @@ class FileLoader:
|
|
|
203
203
|
if self._poll_interval is None:
|
|
204
204
|
return
|
|
205
205
|
|
|
206
|
+
consecutive_error_logs = 0
|
|
207
|
+
max_consecutive_logs = 3
|
|
208
|
+
suppression_logged = False
|
|
209
|
+
|
|
206
210
|
while True:
|
|
207
211
|
try:
|
|
208
212
|
await asyncio.sleep(self._poll_interval)
|
|
209
213
|
try:
|
|
210
214
|
await self.maybe_notify()
|
|
215
|
+
consecutive_error_logs = 0
|
|
216
|
+
suppression_logged = False
|
|
211
217
|
except Exception as e:
|
|
212
|
-
|
|
218
|
+
if consecutive_error_logs < max_consecutive_logs:
|
|
219
|
+
self._log.error(f"Error watching file: {self.path}\n{e!r}", exc_info=e)
|
|
220
|
+
consecutive_error_logs += 1
|
|
221
|
+
elif not suppression_logged:
|
|
222
|
+
self._log.warning(
|
|
223
|
+
"Too many errors while watching %s — suppressing further logs.",
|
|
224
|
+
self.path,
|
|
225
|
+
)
|
|
226
|
+
suppression_logged = True
|
|
227
|
+
|
|
213
228
|
except asyncio.CancelledError:
|
|
214
229
|
break
|
|
215
230
|
|
{jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/pytest_plugin.py
RENAMED
|
@@ -240,7 +240,12 @@ def rtc_add_doc_to_store(rtc_connect_doc_client):
|
|
|
240
240
|
|
|
241
241
|
def rtc_create_SQLite_store_factory(jp_serverapp):
|
|
242
242
|
async def _inner(type: str, path: str, content: str) -> DocumentRoom:
|
|
243
|
-
db = SQLiteYStore(
|
|
243
|
+
db = SQLiteYStore(
|
|
244
|
+
path=f"{type}:{path}",
|
|
245
|
+
# `SQLiteYStore` here is a subclass of booth `LoggingConfigurable`
|
|
246
|
+
# and `pycrdt_websocket.ystore.SQLiteYStore`, but mypy gets lost:
|
|
247
|
+
config=jp_serverapp.config, # type:ignore[call-arg]
|
|
248
|
+
)
|
|
244
249
|
_ = create_task(db.start())
|
|
245
250
|
await db.started.wait()
|
|
246
251
|
|
|
@@ -271,7 +276,7 @@ def rtc_create_mock_document_room():
|
|
|
271
276
|
last_modified: datetime | None = None,
|
|
272
277
|
save_delay: float | None = None,
|
|
273
278
|
store: SQLiteYStore | None = None,
|
|
274
|
-
writable: bool =
|
|
279
|
+
writable: bool = True,
|
|
275
280
|
) -> tuple[FakeContentsManager, FileLoader, DocumentRoom]:
|
|
276
281
|
paths = {id: path}
|
|
277
282
|
|
|
@@ -9,7 +9,7 @@ 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.
|
|
12
|
+
from pycrdt_websocket.yroom import YRoom
|
|
13
13
|
from pycrdt_websocket.ystore import BaseYStore, YDocNotFound
|
|
14
14
|
|
|
15
15
|
from .loaders import FileLoader
|
|
@@ -103,7 +103,7 @@ class DocumentRoom(YRoom):
|
|
|
103
103
|
It is important to set the ready property in the parent class (`self.ready = True`),
|
|
104
104
|
this setter will subscribe for updates on the shared document.
|
|
105
105
|
"""
|
|
106
|
-
if self.ready:
|
|
106
|
+
if self.ready:
|
|
107
107
|
return
|
|
108
108
|
|
|
109
109
|
self.log.info("Initializing room %s", self._room_id)
|
|
@@ -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()
|
|
@@ -285,9 +311,9 @@ class DocumentRoom(YRoom):
|
|
|
285
311
|
"content": self._document.source,
|
|
286
312
|
}
|
|
287
313
|
)
|
|
288
|
-
|
|
289
|
-
self.
|
|
290
|
-
|
|
314
|
+
if saved_model:
|
|
315
|
+
async with self._update_lock:
|
|
316
|
+
self._document.dirty = False
|
|
291
317
|
self._document.hash = saved_model["hash"]
|
|
292
318
|
|
|
293
319
|
self._emit(LogLevel.INFO, "save", "Content saved.")
|
{jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/websocketserver.py
RENAMED
|
@@ -7,9 +7,10 @@ import asyncio
|
|
|
7
7
|
from logging import Logger
|
|
8
8
|
from typing import Any, Callable
|
|
9
9
|
|
|
10
|
-
from pycrdt_websocket.
|
|
10
|
+
from pycrdt_websocket.websocket import Websocket
|
|
11
|
+
from pycrdt_websocket.websocket_server import WebsocketServer
|
|
12
|
+
from pycrdt_websocket.yroom import YRoom
|
|
11
13
|
from pycrdt_websocket.ystore import BaseYStore
|
|
12
|
-
from tornado.websocket import WebSocketHandler
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class RoomNotFound(LookupError):
|
|
@@ -38,7 +39,7 @@ class JupyterWebsocketServer(WebsocketServer):
|
|
|
38
39
|
|
|
39
40
|
def __init__(
|
|
40
41
|
self,
|
|
41
|
-
ystore_class: BaseYStore,
|
|
42
|
+
ystore_class: type[BaseYStore],
|
|
42
43
|
rooms_ready: bool = True,
|
|
43
44
|
auto_clean_rooms: bool = True,
|
|
44
45
|
exception_handler: Callable[[Exception, Logger], bool] | None = None,
|
|
@@ -132,7 +133,7 @@ class JupyterWebsocketServer(WebsocketServer):
|
|
|
132
133
|
await self.start_room(room)
|
|
133
134
|
return room
|
|
134
135
|
|
|
135
|
-
async def serve(self, websocket:
|
|
136
|
+
async def serve(self, websocket: Websocket) -> None:
|
|
136
137
|
# start monitoring here as the event loop is not yet available when initializing the object
|
|
137
138
|
if self.monitor_task is None:
|
|
138
139
|
self.monitor_task = asyncio.create_task(self._monitor())
|
|
@@ -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.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter-config/jupyter_server_ydoc.json
RENAMED
|
File without changes
|
|
File without changes
|
{jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/events/awareness.yaml
RENAMED
|
File without changes
|
{jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/events/fork.yaml
RENAMED
|
File without changes
|
{jupyter_server_ydoc-2.0.1 → jupyter_server_ydoc-2.1.0a0}/jupyter_server_ydoc/events/session.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|