jupyter-server-ydoc 2.0.1__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/app.py +2 -2
- jupyter_server_ydoc/handlers.py +53 -37
- jupyter_server_ydoc/loaders.py +16 -1
- jupyter_server_ydoc/pytest_plugin.py +7 -2
- jupyter_server_ydoc/rooms.py +33 -6
- jupyter_server_ydoc/utils.py +1 -0
- jupyter_server_ydoc/websocketserver.py +5 -4
- {jupyter_server_ydoc-2.0.1.dist-info → jupyter_server_ydoc-2.1.0.dist-info}/METADATA +6 -1
- jupyter_server_ydoc-2.1.0.dist-info/RECORD +19 -0
- jupyter_server_ydoc-2.0.1.dist-info/RECORD +0 -19
- {jupyter_server_ydoc-2.0.1.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.1.dist-info → jupyter_server_ydoc-2.1.0.dist-info}/WHEEL +0 -0
- {jupyter_server_ydoc-2.0.1.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/app.py
CHANGED
|
@@ -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)
|
jupyter_server_ydoc/handlers.py
CHANGED
|
@@ -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,
|
|
19
|
-
from pycrdt_websocket.
|
|
18
|
+
from pycrdt import Doc, Encoder, 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()
|
|
@@ -269,7 +273,7 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
|
|
|
269
273
|
if self._room_id != "JupyterLab:globalAwareness":
|
|
270
274
|
self._emit_awareness_event(self.current_user.username, "join")
|
|
271
275
|
|
|
272
|
-
async def send(self, message):
|
|
276
|
+
async def send(self, message: bytes) -> None:
|
|
273
277
|
"""
|
|
274
278
|
Send a message to the client.
|
|
275
279
|
"""
|
|
@@ -290,38 +294,50 @@ 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
|
-
"timestamp": time.time(),
|
|
303
|
-
"content": json.loads(msg),
|
|
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,
|
|
304
306
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
|
314
324
|
|
|
315
325
|
self._message_queue.put_nowait(message)
|
|
316
326
|
self._websocket_server.ypatch_nb += 1
|
|
317
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
|
+
|
|
318
334
|
def on_close(self) -> None:
|
|
319
335
|
"""
|
|
320
336
|
On connection close.
|
|
321
337
|
"""
|
|
322
338
|
# stop serving this client
|
|
323
339
|
self._message_queue.put_nowait(b"")
|
|
324
|
-
if isinstance(self.room, DocumentRoom) and self.room.clients ==
|
|
340
|
+
if isinstance(self.room, DocumentRoom) and self.room.clients == {self}:
|
|
325
341
|
# no client in this room after we disconnect
|
|
326
342
|
# keep the document for a while in case someone reconnects
|
|
327
343
|
self.log.info("Cleaning room: %s", self._room_id)
|
|
@@ -386,9 +402,7 @@ class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
|
|
|
386
402
|
self._emit(LogLevel.INFO, "clean", "Loader deleted.")
|
|
387
403
|
del self._room_locks[self._room_id]
|
|
388
404
|
|
|
389
|
-
def _on_global_awareness_event(
|
|
390
|
-
self, topic: Literal["change", "update"], changes: tuple[dict[str, Any], Any]
|
|
391
|
-
) -> None:
|
|
405
|
+
def _on_global_awareness_event(self, topic: str, changes: tuple[dict[str, Any], Any]) -> None:
|
|
392
406
|
"""
|
|
393
407
|
Update the users when the global awareness changes.
|
|
394
408
|
|
|
@@ -489,7 +503,7 @@ class TimelineHandler(APIHandler):
|
|
|
489
503
|
try:
|
|
490
504
|
room_id = room_id_from_encoded_path(encoded_path)
|
|
491
505
|
room: YRoom = await self.ywebsocket_server.get_room(room_id)
|
|
492
|
-
fork_ydoc = Doc()
|
|
506
|
+
fork_ydoc: Doc = Doc()
|
|
493
507
|
|
|
494
508
|
ydoc_factory = YDOCS.get(content_type)
|
|
495
509
|
if ydoc_factory is None:
|
|
@@ -505,7 +519,9 @@ class TimelineHandler(APIHandler):
|
|
|
505
519
|
FORK_DOCUMENTS[idx] = ydoc_factory(fork_ydoc)
|
|
506
520
|
undo_manager: UndoManager = FORK_DOCUMENTS[idx].undo_manager
|
|
507
521
|
|
|
508
|
-
|
|
522
|
+
ystore = room.ystore
|
|
523
|
+
assert ystore
|
|
524
|
+
updates_and_timestamps = [(item[0], item[-1]) async for item in ystore.read()]
|
|
509
525
|
|
|
510
526
|
result_timestamps = []
|
|
511
527
|
|
|
@@ -649,7 +665,7 @@ class DocForkHandler(APIHandler):
|
|
|
649
665
|
return self.finish({"code": 404, "error": "Root room not found"})
|
|
650
666
|
|
|
651
667
|
update = root_room.ydoc.get_update()
|
|
652
|
-
fork_ydoc = Doc()
|
|
668
|
+
fork_ydoc: Doc = Doc()
|
|
653
669
|
fork_ydoc.apply_update(update)
|
|
654
670
|
model = self.get_json_body()
|
|
655
671
|
synchronize = model.get("synchronize", False)
|
jupyter_server_ydoc/loaders.py
CHANGED
|
@@ -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
|
|
|
@@ -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
|
|
jupyter_server_ydoc/rooms.py
CHANGED
|
@@ -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,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()
|
|
@@ -285,9 +312,9 @@ class DocumentRoom(YRoom):
|
|
|
285
312
|
"content": self._document.source,
|
|
286
313
|
}
|
|
287
314
|
)
|
|
288
|
-
|
|
289
|
-
self.
|
|
290
|
-
|
|
315
|
+
if saved_model:
|
|
316
|
+
async with self._update_lock:
|
|
317
|
+
self._document.dirty = False
|
|
291
318
|
self._document.hash = saved_model["hash"]
|
|
292
319
|
|
|
293
320
|
self._emit(LogLevel.INFO, "save", "Content saved.")
|
jupyter_server_ydoc/utils.py
CHANGED
|
@@ -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())
|
|
@@ -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
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
jupyter_server_ydoc/__init__.py,sha256=B8H7XLhzgrTCQD8304Lx91FYXslwabsnV9OuYu4M4Hw,346
|
|
2
|
+
jupyter_server_ydoc/_version.py,sha256=Xybt2skBZamGMNlLuOX1IG-h4uIxqUDGAO8MIGWrJac,22
|
|
3
|
+
jupyter_server_ydoc/app.py,sha256=6Zca0acaEFCKyUbXdmcPmVk1Dgu91Y-DFjYR16WKFlg,8161
|
|
4
|
+
jupyter_server_ydoc/handlers.py,sha256=x3R5VluXWFN460AiC7xNbipSpCR7SQSjqrcwjBorgpw,28836
|
|
5
|
+
jupyter_server_ydoc/loaders.py,sha256=XUQqg2EbfQUYlQVjHY183gYKeVZ6x92VHy4EsOQz4fA,11303
|
|
6
|
+
jupyter_server_ydoc/pytest_plugin.py,sha256=yMVlk7fvHXiM7g6ua0Ophc96cFwC0bmTWVDbbUeu2DE,8741
|
|
7
|
+
jupyter_server_ydoc/rooms.py,sha256=O0dYKeDRMUPFRraFJf8irqnFaSD80hirrtAYBXKTgrM,13103
|
|
8
|
+
jupyter_server_ydoc/stores.py,sha256=_5J6eNs3R5Tv88PCc-GGuszxQstfvNoBCYABqzBzJXA,1004
|
|
9
|
+
jupyter_server_ydoc/test_utils.py,sha256=utUwB5FThc_SCQshhUbLNih9GUa5qBcmMgU6-jx0ZnA,2275
|
|
10
|
+
jupyter_server_ydoc/utils.py,sha256=_wI4CFOEZK4MSMYCZe2moSbggUTRdGxY4qTzSDEFzdE,2183
|
|
11
|
+
jupyter_server_ydoc/websocketserver.py,sha256=h1yTgJcsCK17_97Ne5x-lbgIFsxylwnltxagcuAlTJY,5185
|
|
12
|
+
jupyter_server_ydoc/events/awareness.yaml,sha256=2FrCci5rZIaU4rn8pIPZJkd132YAZdzKjSNSwjOY7Dk,755
|
|
13
|
+
jupyter_server_ydoc/events/fork.yaml,sha256=3OrhQjhVyLjlBJWMiffbnZodL3GzFafLwEmSBFrK33o,1303
|
|
14
|
+
jupyter_server_ydoc/events/session.yaml,sha256=PS0MxowpRwY5QFYm-LJvHUxKHnsictV8_6VEwfhYxcQ,1596
|
|
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,,
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
jupyter_server_ydoc/__init__.py,sha256=B8H7XLhzgrTCQD8304Lx91FYXslwabsnV9OuYu4M4Hw,346
|
|
2
|
-
jupyter_server_ydoc/_version.py,sha256=wAxkK8w13vqoF47A8iqWdSlIgRRXmZiQ0R4wePZfzhs,22
|
|
3
|
-
jupyter_server_ydoc/app.py,sha256=JqkpijoPdo_qum0CKqbG-I6W8fpC3-v2cFA3kFxK3mg,8111
|
|
4
|
-
jupyter_server_ydoc/handlers.py,sha256=Q0A638Ke_TR-hSdAgyz8u1gIOCk3FIqWRhV-QH2PLMM,27977
|
|
5
|
-
jupyter_server_ydoc/loaders.py,sha256=rUPZZsMIxcE7ZEWTY8O2_ib7DUCmLvvdptqZYpsusdY,10676
|
|
6
|
-
jupyter_server_ydoc/pytest_plugin.py,sha256=M9dQhiyDnGxFWxoGDj5l5lRVyfkYFKdU2i1Yj_Z16sU,8525
|
|
7
|
-
jupyter_server_ydoc/rooms.py,sha256=szOAfMldhQIrmVpqoF75O0_KXY54X_TrzJz6vpjR6kE,12254
|
|
8
|
-
jupyter_server_ydoc/stores.py,sha256=_5J6eNs3R5Tv88PCc-GGuszxQstfvNoBCYABqzBzJXA,1004
|
|
9
|
-
jupyter_server_ydoc/test_utils.py,sha256=utUwB5FThc_SCQshhUbLNih9GUa5qBcmMgU6-jx0ZnA,2275
|
|
10
|
-
jupyter_server_ydoc/utils.py,sha256=EgKC15js8VOS8-5jGMs4pfHQfV9drnNT2Gew5UlyXZc,2171
|
|
11
|
-
jupyter_server_ydoc/websocketserver.py,sha256=7fLPJcWczD-4R_-LXtfvNxM_pUXFasZWDmT4RIrOQHE,5150
|
|
12
|
-
jupyter_server_ydoc/events/awareness.yaml,sha256=2FrCci5rZIaU4rn8pIPZJkd132YAZdzKjSNSwjOY7Dk,755
|
|
13
|
-
jupyter_server_ydoc/events/fork.yaml,sha256=3OrhQjhVyLjlBJWMiffbnZodL3GzFafLwEmSBFrK33o,1303
|
|
14
|
-
jupyter_server_ydoc/events/session.yaml,sha256=PS0MxowpRwY5QFYm-LJvHUxKHnsictV8_6VEwfhYxcQ,1596
|
|
15
|
-
jupyter_server_ydoc-2.0.1.data/data/etc/jupyter/jupyter_server_config.d/jupyter_collaboration.json,sha256=0thh2hJUxAKkZSmneJMG0U6QJRjdM6zGlwrTedEt-Jk,94
|
|
16
|
-
jupyter_server_ydoc-2.0.1.dist-info/METADATA,sha256=-hxNueyhVsO-DXfdiYoiBpQ_rvk6MU9OwRnh-xHg3LA,5115
|
|
17
|
-
jupyter_server_ydoc-2.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
18
|
-
jupyter_server_ydoc-2.0.1.dist-info/licenses/LICENSE,sha256=mhO0ZW9EiWOPg0dUgB-lNbJ0CGwRmTdbeAg_se1SOnY,2833
|
|
19
|
-
jupyter_server_ydoc-2.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{jupyter_server_ydoc-2.0.1.dist-info → jupyter_server_ydoc-2.1.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|