Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-jupyter-server-ydoc for
openSUSE:Factory checked in at 2025-01-31 16:02:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jupyter-server-ydoc (Old)
and /work/SRC/openSUSE:Factory/.python-jupyter-server-ydoc.new.2316 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jupyter-server-ydoc"
Fri Jan 31 16:02:23 2025 rev:2 rq:1241239 version:1.1.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-jupyter-server-ydoc/python-jupyter-server-ydoc.changes
2024-11-08 12:04:23.149993953 +0100
+++
/work/SRC/openSUSE:Factory/.python-jupyter-server-ydoc.new.2316/python-jupyter-server-ydoc.changes
2025-01-31 16:02:46.391947333 +0100
@@ -1,0 +2,6 @@
+Wed Jan 29 15:47:01 UTC 2025 - Ben Greiner <[email protected]>
+
+- Update to 1.1.0
+ * New subpackage version for jupyter-collaboration 3.1.0
+
+-------------------------------------------------------------------
Old:
----
jupyter_server_ydoc-1.0.0.tar.gz
New:
----
jupyter_server_ydoc-1.1.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-jupyter-server-ydoc.spec ++++++
--- /var/tmp/diff_new_pack.sxhIFI/_old 2025-01-31 16:02:46.963970281 +0100
+++ /var/tmp/diff_new_pack.sxhIFI/_new 2025-01-31 16:02:46.967970441 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-jupyter-server-ydoc
#
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2025 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -25,9 +25,9 @@
%bcond_with test
%endif
-%define distversion 1
+%define distversion 1.1
Name: python-jupyter-server-ydoc%{psuffix}
-Version: 1.0.0
+Version: 1.1.0
Release: 0
Summary: Jupyter server extension integrating collaborative shared
models
License: BSD-3-Clause
@@ -76,17 +76,14 @@
%package test
Summary: The jupyter_server_ydoc[test] extra
-Requires: python-jupyter-server-ydoc = %{version}
-Requires: python-jsonschema >= 4.18.0
-Requires: python-jupyter-server-fileid >= 0.7
+Requires: python-anyio
+Requires: python-dirty-equals
+Requires: python-httpx-ws >= 0.5.2
+Requires: python-jupyter-server-fileid
Requires: python-jupyter-server-test >= 2.11.1
-Requires: python-jupyter_events >= 0.10.0
-Requires: python-jupyter_ydoc >= 2.1.2
-Requires: python-pycrdt-websocket >= 0.15.0
-Requires: python-pycrdt
+Requires: python-jupyter-server-ydoc = %{version}
Requires: python-pytest >= 7.0
-Requires: python-pytest-jupyter
-Requires: python-websockets
+Requires: (python-importlib_metadata >= 4.8.3 if python-base < 3.10)
%description test
Metapackage for the jupyter_server_ydoc[test] extra requirements
++++++ jupyter_server_ydoc-1.0.0.tar.gz -> jupyter_server_ydoc-1.1.0.tar.gz
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server_ydoc-1.0.0/PKG-INFO
new/jupyter_server_ydoc-1.1.0/PKG-INFO
--- old/jupyter_server_ydoc-1.0.0/PKG-INFO 2020-02-02 01:00:00.000000000
+0100
+++ new/jupyter_server_ydoc-1.1.0/PKG-INFO 2020-02-02 01:00:00.000000000
+0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.3
Name: jupyter-server-ydoc
-Version: 1.0.0
+Version: 1.1.0
Summary: jupyter-server extension integrating collaborative shared models.
Author-email: Jupyter Development Team <[email protected]>
License: # Licensing terms
@@ -62,7 +62,6 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
-License-File: LICENSE
Classifier: Framework :: Jupyter
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
@@ -79,17 +78,19 @@
Requires-Dist: jupyter-events>=0.10.0
Requires-Dist: jupyter-server-fileid<1,>=0.7.0
Requires-Dist: jupyter-server<3.0.0,>=2.11.1
-Requires-Dist: jupyter-ydoc<4.0.0,>=2.1.2
+Requires-Dist: jupyter-ydoc!=3.0.0,!=3.0.1,<4.0.0,>=2.1.2
Requires-Dist: pycrdt
Requires-Dist: pycrdt-websocket<0.16.0,>=0.15.0
Provides-Extra: test
+Requires-Dist: anyio; extra == 'test'
Requires-Dist: coverage; extra == 'test'
+Requires-Dist: dirty-equals; extra == 'test'
+Requires-Dist: httpx-ws>=0.5.2; extra == 'test'
Requires-Dist: importlib-metadata>=4.8.3; (python_version < '3.10') and extra
== 'test'
Requires-Dist: jupyter-server-fileid[test]; extra == 'test'
Requires-Dist: jupyter-server[test]>=2.4.0; extra == 'test'
Requires-Dist: pytest-cov; extra == 'test'
Requires-Dist: pytest>=7.0; extra == 'test'
-Requires-Dist: websockets; extra == 'test'
Description-Content-Type: text/markdown
# jupyter-server-ydoc
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/_version.py
new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/_version.py
--- old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/_version.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/_version.py
2020-02-02 01:00:00.000000000 +0100
@@ -1 +1 @@
-__version__ = "1.0.0"
+__version__ = "1.1.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/app.py
new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/app.py
--- old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/app.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/app.py 2020-02-02
01:00:00.000000000 +0100
@@ -14,6 +14,7 @@
from traitlets import Bool, Float, Type
from .handlers import (
+ DocForkHandler,
DocSessionHandler,
TimelineHandler,
UndoRedoHandler,
@@ -25,6 +26,7 @@
from .utils import (
AWARENESS_EVENTS_SCHEMA_PATH,
EVENTS_SCHEMA_PATH,
+ FORK_EVENTS_SCHEMA_PATH,
encode_file_path,
room_id_from_encoded_path,
)
@@ -85,6 +87,7 @@
super().initialize()
self.serverapp.event_logger.register_event_schema(EVENTS_SCHEMA_PATH)
self.serverapp.event_logger.register_event_schema(AWARENESS_EVENTS_SCHEMA_PATH)
+
self.serverapp.event_logger.register_event_schema(FORK_EVENTS_SCHEMA_PATH)
def initialize_settings(self):
self.settings.update(
@@ -124,6 +127,13 @@
self.handlers.extend(
[
(
+ r"/api/collaboration/fork/(.*)",
+ DocForkHandler,
+ {
+ "ywebsocket_server": self.ywebsocket_server,
+ },
+ ),
+ (
r"/api/collaboration/room/(.*)",
YDocWebSocketHandler,
{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/events/fork.yaml
new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/events/fork.yaml
--- old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/events/fork.yaml
1970-01-01 01:00:00.000000000 +0100
+++ new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/events/fork.yaml
2020-02-02 01:00:00.000000000 +0100
@@ -0,0 +1,56 @@
+"$id": https://schema.jupyter.org/jupyter_collaboration/fork/v1
+"$schema": "http://json-schema.org/draft-07/schema"
+version: 1
+title: Collaborative fork events
+personal-data: true
+description: |
+ Fork events emitted from server-side during a collaborative session.
+type: object
+required:
+ - fork_roomid
+ - fork_info
+ - username
+ - action
+properties:
+ fork_roomid:
+ type: string
+ description: |
+ Fork root room ID.
+ fork_info:
+ type: object
+ description: |
+ Fork root room information.
+ required:
+ - root_roomid
+ - synchronize
+ - title
+ - description
+ properties:
+ root_roomid:
+ type: string
+ description: |
+ Root room ID. Usually composed by the file type, format and ID.
+ synchronize:
+ type: boolean
+ description: |
+ Whether the fork is kept in sync with the root.
+ title:
+ type: string
+ description: |
+ The title of the fork.
+ description:
+ type: string
+ description: |
+ The description of the fork.
+ username:
+ type: string
+ description: |
+ The name of the user who created or deleted the fork.
+ action:
+ enum:
+ - create
+ - delete
+ description: |
+ Possible values:
+ 1. create
+ 2. delete
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/handlers.py
new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/handlers.py
--- old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/handlers.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/handlers.py
2020-02-02 01:00:00.000000000 +0100
@@ -26,6 +26,7 @@
from .utils import (
JUPYTER_COLLABORATION_AWARENESS_EVENTS_URI,
JUPYTER_COLLABORATION_EVENTS_URI,
+ JUPYTER_COLLABORATION_FORK_EVENTS_URI,
LogLevel,
MessageType,
decode_file_path,
@@ -39,6 +40,7 @@
SERVER_SESSION = str(uuid.uuid4())
FORK_DOCUMENTS = {}
+FORK_ROOMS: dict[str, dict[str, str]] = {}
class YDocWebSocketHandler(WebSocketHandler, JupyterHandler):
@@ -600,3 +602,101 @@
if room_id in FORK_DOCUMENTS:
del FORK_DOCUMENTS[room_id]
self.log.info(f"Fork Document for {room_id} has been removed.")
+
+
+class DocForkHandler(APIHandler):
+ """
+ Jupyter Server handler to:
+ - create a fork of a root document (optionally synchronizing with the root
document),
+ - delete a fork of a root document (optionally merging back in the root
document).
+ - get fork IDs of a root document.
+ """
+
+ auth_resource = "contents"
+
+ def initialize(
+ self,
+ ywebsocket_server: JupyterWebsocketServer,
+ ) -> None:
+ self._websocket_server = ywebsocket_server
+
+ @web.authenticated
+ @authorized
+ async def get(self, root_roomid):
+ """
+ Returns a dictionary of fork room ID to fork room information for the
given root room ID.
+ """
+ self.write(
+ {
+ fork_roomid: fork_info
+ for fork_roomid, fork_info in FORK_ROOMS.items()
+ if fork_info["root_roomid"] == root_roomid
+ }
+ )
+
+ @web.authenticated
+ @authorized
+ async def put(self, root_roomid):
+ """
+ Creates a fork of a root document and returns its ID.
+ Optionally keeps the fork in sync with the root.
+ """
+ fork_roomid = uuid4().hex
+ root_room = await self._websocket_server.get_room(root_roomid)
+ update = root_room.ydoc.get_update()
+ fork_ydoc = Doc()
+ fork_ydoc.apply_update(update)
+ model = self.get_json_body()
+ synchronize = model.get("synchronize", False)
+ if synchronize:
+ root_room.ydoc.observe(lambda event:
fork_ydoc.apply_update(event.update))
+ FORK_ROOMS[fork_roomid] = fork_info = {
+ "root_roomid": root_roomid,
+ "synchronize": synchronize,
+ "title": model.get("title", ""),
+ "description": model.get("description", ""),
+ }
+ fork_room = YRoom(ydoc=fork_ydoc)
+ self._websocket_server.rooms[fork_roomid] = fork_room
+ await self._websocket_server.start_room(fork_room)
+ self._emit_fork_event(self.current_user.username, fork_roomid,
fork_info, "create")
+ data = json.dumps(
+ {
+ "sessionId": SERVER_SESSION,
+ "fork_roomid": fork_roomid,
+ "fork_info": fork_info,
+ }
+ )
+ self.set_status(201)
+ return self.finish(data)
+
+ @web.authenticated
+ @authorized
+ async def delete(self, fork_roomid):
+ """
+ Deletes a forked document, and optionally merges it back in the root
document.
+ """
+ fork_info = FORK_ROOMS[fork_roomid]
+ root_roomid = fork_info["root_roomid"]
+ del FORK_ROOMS[fork_roomid]
+ if self.get_query_argument("merge") == "true":
+ root_room = await self._websocket_server.get_room(root_roomid)
+ root_ydoc = root_room.ydoc
+ fork_room = await self._websocket_server.get_room(fork_roomid)
+ fork_ydoc = fork_room.ydoc
+ fork_update = fork_ydoc.get_update()
+ root_ydoc.apply_update(fork_update)
+ await self._websocket_server.delete_room(name=fork_roomid)
+ self._emit_fork_event(self.current_user.username, fork_roomid,
fork_info, "delete")
+ self.set_status(200)
+
+ def _emit_fork_event(
+ self, username: str, fork_roomid: str, fork_info: dict[str, str],
action: str
+ ) -> None:
+ data = {
+ "username": username,
+ "fork_roomid": fork_roomid,
+ "fork_info": fork_info,
+ "action": action,
+ }
+
self.event_logger.emit(schema_id=JUPYTER_COLLABORATION_FORK_EVENTS_URI,
data=data)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/pytest_plugin.py
new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/pytest_plugin.py
--- old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/pytest_plugin.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/pytest_plugin.py
2020-02-02 01:00:00.000000000 +0100
@@ -9,14 +9,19 @@
import nbformat
import pytest
+from httpx_ws import aconnect_ws
from jupyter_server_ydoc.loaders import FileLoader
from jupyter_server_ydoc.rooms import DocumentRoom
from jupyter_server_ydoc.stores import SQLiteYStore
from jupyter_ydoc import YNotebook, YUnicode
from pycrdt_websocket import WebsocketProvider
-from websockets import connect
-from .test_utils import FakeContentsManager, FakeEventLogger, FakeFileIDManager
+from .test_utils import (
+ FakeContentsManager,
+ FakeEventLogger,
+ FakeFileIDManager,
+ Websocket,
+)
@pytest.fixture
@@ -126,8 +131,8 @@
@pytest.fixture
def rtc_connect_awareness_client(jp_http_port, jp_base_url):
async def _inner(room_id: str) -> Any:
- return connect(
-
f"ws://127.0.0.1:{jp_http_port}{jp_base_url}api/collaboration/room/{room_id}"
+ return aconnect_ws(
+
f"http://127.0.0.1:{jp_http_port}{jp_base_url}api/collaboration/room/{room_id}"
)
return _inner
@@ -138,8 +143,71 @@
async def _inner(format: str, type: str, path: str) -> Any:
resp = await rtc_fetch_session(format, type, path)
data = json.loads(resp.body.decode("utf-8"))
- return connect(
-
f"ws://127.0.0.1:{jp_http_port}{jp_base_url}api/collaboration/room/{data['format']}:{data['type']}:{data['fileId']}?sessionId={data['sessionId']}"
+ room_name = f"{data['format']}:{data['type']}:{data['fileId']}"
+ return (
+ aconnect_ws(
+
f"http://127.0.0.1:{jp_http_port}{jp_base_url}api/collaboration/room/{room_name}?sessionId={data['sessionId']}"
+ ),
+ room_name,
+ )
+
+ return _inner
+
+
[email protected]
+def rtc_connect_fork_client(jp_http_port, jp_base_url, rtc_fetch_session):
+ async def _inner(room_id: str) -> Any:
+ return aconnect_ws(
+
f"http://127.0.0.1:{jp_http_port}{jp_base_url}api/collaboration/room/{room_id}"
+ )
+
+ return _inner
+
+
[email protected]
+def rtc_get_forks_client(jp_fetch):
+ async def _inner(root_roomid: str) -> Any:
+ return await jp_fetch(
+ "/api/collaboration/fork",
+ root_roomid,
+ method="GET",
+ )
+
+ return _inner
+
+
[email protected]
+def rtc_create_fork_client(jp_fetch):
+ async def _inner(
+ root_roomid: str,
+ synchronize: bool,
+ title: str | None = None,
+ description: str | None = None,
+ ) -> Any:
+ return await jp_fetch(
+ "/api/collaboration/fork",
+ root_roomid,
+ method="PUT",
+ body=json.dumps(
+ {
+ "synchronize": synchronize,
+ "title": title,
+ "description": description,
+ }
+ ),
+ )
+
+ return _inner
+
+
[email protected]
+def rtc_delete_fork_client(jp_fetch):
+ async def _inner(fork_roomid: str, merge: bool) -> Any:
+ return await jp_fetch(
+ "/api/collaboration/fork",
+ fork_roomid,
+ method="DELETE",
+ params={"merge": str(merge).lower()},
)
return _inner
@@ -162,9 +230,8 @@
doc.observe(_on_document_change)
- async with await rtc_connect_doc_client(format, type, path) as ws,
WebsocketProvider(
- doc.ydoc, ws
- ):
+ websocket, room_name = await rtc_connect_doc_client(format, type, path)
+ async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws,
room_name)):
await event.wait()
await sleep(0.1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/test_utils.py
new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/test_utils.py
--- old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/test_utils.py
2020-02-02 01:00:00.000000000 +0100
+++ new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/test_utils.py
2020-02-02 01:00:00.000000000 +0100
@@ -6,6 +6,7 @@
from datetime import datetime
from typing import Any
+from anyio import Lock
from jupyter_server import _tz as tz
@@ -55,3 +56,32 @@
class FakeEventLogger:
def emit(self, schema_id: str, data: dict) -> None:
print(data)
+
+
+class Websocket:
+ def __init__(self, websocket: Any, path: str):
+ self._websocket = websocket
+ self._path = path
+ self._send_lock = Lock()
+
+ @property
+ def path(self) -> str:
+ return self._path
+
+ def __aiter__(self):
+ return self
+
+ async def __anext__(self) -> bytes:
+ try:
+ message = await self.recv()
+ except Exception:
+ raise StopAsyncIteration()
+ return message
+
+ async def send(self, message: bytes) -> None:
+ async with self._send_lock:
+ await self._websocket.send_bytes(message)
+
+ async def recv(self) -> bytes:
+ b = await self._websocket.receive_bytes()
+ return bytes(b)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/utils.py
new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/utils.py
--- old/jupyter_server_ydoc-1.0.0/jupyter_server_ydoc/utils.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/utils.py 2020-02-02
01:00:00.000000000 +0100
@@ -11,7 +11,9 @@
JUPYTER_COLLABORATION_AWARENESS_EVENTS_URI = (
"https://schema.jupyter.org/jupyter_collaboration/awareness/v1"
)
+JUPYTER_COLLABORATION_FORK_EVENTS_URI =
"https://schema.jupyter.org/jupyter_collaboration/fork/v1"
AWARENESS_EVENTS_SCHEMA_PATH = EVENTS_FOLDER_PATH / "awareness.yaml"
+FORK_EVENTS_SCHEMA_PATH = EVENTS_FOLDER_PATH / "fork.yaml"
class MessageType(IntEnum):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server_ydoc-1.0.0/pyproject.toml
new/jupyter_server_ydoc-1.1.0/pyproject.toml
--- old/jupyter_server_ydoc-1.0.0/pyproject.toml 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server_ydoc-1.1.0/pyproject.toml 2020-02-02
01:00:00.000000000 +0100
@@ -29,7 +29,7 @@
]
dependencies = [
"jupyter_server>=2.11.1,<3.0.0",
- "jupyter_ydoc>=2.1.2,<4.0.0",
+ "jupyter_ydoc>=2.1.2,<4.0.0,!=3.0.0,!=3.0.1",
"pycrdt",
"pycrdt-websocket>=0.15.0,<0.16.0",
"jupyter_events>=0.10.0",
@@ -41,11 +41,13 @@
[project.optional-dependencies]
test = [
"coverage",
+ "dirty-equals",
"jupyter_server[test]>=2.4.0",
"jupyter_server_fileid[test]",
"pytest>=7.0",
"pytest-cov",
- "websockets",
+ "anyio",
+ "httpx-ws >=0.5.2",
"importlib_metadata >=4.8.3; python_version<'3.10'",
]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server_ydoc-1.0.0/tests/test_documents.py
new/jupyter_server_ydoc-1.1.0/tests/test_documents.py
--- old/jupyter_server_ydoc-1.0.0/tests/test_documents.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server_ydoc-1.1.0/tests/test_documents.py 2020-02-02
01:00:00.000000000 +0100
@@ -11,6 +11,7 @@
import pytest
from anyio import create_task_group, sleep
+from jupyter_server_ydoc.test_utils import Websocket
from pycrdt_websocket import WebsocketProvider
jupyter_ydocs = {ep.name: ep.load() for ep in
entry_points(group="jupyter_ydoc")}
@@ -32,12 +33,12 @@
await rtc_create_file(file_path)
jupyter_ydoc = jupyter_ydocs[file_type]()
- async with await rtc_connect_doc_client(file_format, file_type, file_path)
as ws:
- async with WebsocketProvider(jupyter_ydoc.ydoc, ws):
- for _ in range(2):
- jupyter_ydoc.dirty = True
- await sleep(rtc_document_save_delay * 1.5)
- assert not jupyter_ydoc.dirty
+ websocket, room_name = await rtc_connect_doc_client(file_format,
file_type, file_path)
+ async with websocket as ws, WebsocketProvider(jupyter_ydoc.ydoc,
Websocket(ws, room_name)):
+ for _ in range(2):
+ jupyter_ydoc.dirty = True
+ await sleep(rtc_document_save_delay * 1.5)
+ assert not jupyter_ydoc.dirty
async def cleanup(jp_serverapp):
@@ -59,7 +60,8 @@
await rtc_create_file(file_path)
async def connect(file_format, file_type, file_path):
- async with await rtc_connect_doc_client(file_format, file_type,
file_path) as ws:
+ websocket, room_name = await rtc_connect_doc_client(file_format,
file_type, file_path)
+ async with websocket:
pass
t0 = time()
@@ -84,7 +86,8 @@
async def connect(file_format, file_type, file_path):
t0 = time()
- async with await rtc_connect_doc_client(file_format, file_type,
file_path) as ws:
+ websocket, room_name = await rtc_connect_doc_client(file_format,
file_type, file_path)
+ async with websocket:
pass
t1 = time()
return t1 - t0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/jupyter_server_ydoc-1.0.0/tests/test_handlers.py
new/jupyter_server_ydoc-1.1.0/tests/test_handlers.py
--- old/jupyter_server_ydoc-1.0.0/tests/test_handlers.py 2020-02-02
01:00:00.000000000 +0100
+++ new/jupyter_server_ydoc-1.1.0/tests/test_handlers.py 2020-02-02
01:00:00.000000000 +0100
@@ -7,8 +7,11 @@
from asyncio import Event, sleep
from typing import Any
+from dirty_equals import IsStr
from jupyter_events.logger import EventLogger
+from jupyter_server_ydoc.test_utils import Websocket
from jupyter_ydoc import YUnicode
+from pycrdt import Text
from pycrdt_websocket import WebsocketProvider
@@ -77,9 +80,8 @@
doc = YUnicode()
doc.observe(_on_document_change)
- async with await rtc_connect_doc_client("text", "file", path) as ws,
WebsocketProvider(
- doc.ydoc, ws
- ):
+ websocket, room_name = await rtc_connect_doc_client("text", "file", path)
+ async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws,
room_name)):
await event.wait()
await sleep(0.1)
@@ -114,9 +116,8 @@
listener=my_listener,
)
- async with await rtc_connect_doc_client("text", "file", path) as ws,
WebsocketProvider(
- doc.ydoc, ws
- ):
+ websocket, room_name = await rtc_connect_doc_client("text", "file", path)
+ async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws,
room_name)):
await event.wait()
await sleep(0.1)
@@ -147,9 +148,8 @@
doc = YUnicode()
doc.observe(_on_document_change)
- async with await rtc_connect_doc_client("text", "file", path) as ws,
WebsocketProvider(
- doc.ydoc, ws
- ):
+ websocket, room_name = await rtc_connect_doc_client("text", "file", path)
+ async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws,
room_name)):
await event.wait()
await sleep(0.1)
@@ -173,18 +173,16 @@
path2, _ = await rtc_create_file("test2.txt", "test2")
try:
- async with await rtc_connect_doc_client("text2", "file2", path2) as
ws, WebsocketProvider(
- doc.ydoc, ws
- ):
+ websocket, room_name = await rtc_connect_doc_client("text2", "file2",
path2)
+ async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws,
room_name)):
await event.wait()
await sleep(0.1)
except Exception:
pass
try:
- async with await rtc_connect_doc_client("text2", "file2", path2) as
ws, WebsocketProvider(
- doc.ydoc, ws
- ):
+ websocket, room_name = await rtc_connect_doc_client("text2", "file2",
path2)
+ async with websocket as ws, WebsocketProvider(doc.ydoc, Websocket(ws,
room_name)):
await event.wait()
await sleep(0.1)
except Exception:
@@ -215,3 +213,147 @@
await jp_serverapp.web_app.settings["jupyter_server_ydoc"].stop_extension()
del jp_serverapp.web_app.settings["file_id_manager"]
+
+
+async def test_fork_handler(
+ jp_serverapp,
+ rtc_create_file,
+ rtc_connect_doc_client,
+ rtc_connect_fork_client,
+ rtc_get_forks_client,
+ rtc_create_fork_client,
+ rtc_delete_fork_client,
+ rtc_fetch_session,
+):
+ collected_data = []
+
+ async def my_listener(logger: EventLogger, schema_id: str, data: dict) ->
None:
+ collected_data.append(data)
+
+ event_logger = jp_serverapp.event_logger
+ event_logger.add_listener(
+ schema_id="https://schema.jupyter.org/jupyter_collaboration/fork/v1",
+ listener=my_listener,
+ )
+
+ path, _ = await rtc_create_file("test.txt", "Hello")
+
+ root_connect_event = Event()
+
+ def _on_root_change(topic: str, event: Any) -> None:
+ if topic == "source":
+ root_connect_event.set()
+
+ root_ydoc = YUnicode()
+ root_ydoc.observe(_on_root_change)
+
+ resp = await rtc_fetch_session("text", "file", path)
+ data = json.loads(resp.body.decode("utf-8"))
+ file_id = data["fileId"]
+ root_roomid = f"text:file:{file_id}"
+
+ websocket, room_name = await rtc_connect_doc_client("text", "file", path)
+ async with websocket as ws, WebsocketProvider(root_ydoc.ydoc,
Websocket(ws, room_name)):
+ await root_connect_event.wait()
+
+ resp = await rtc_create_fork_client(root_roomid, False, "my fork0",
"is awesome0")
+ data = json.loads(resp.body.decode("utf-8"))
+ fork_roomid0 = data["fork_roomid"]
+
+ resp = await rtc_get_forks_client(root_roomid)
+ data = json.loads(resp.body.decode("utf-8"))
+ expected_data0 = {
+ fork_roomid0: {
+ "root_roomid": root_roomid,
+ "synchronize": False,
+ "title": "my fork0",
+ "description": "is awesome0",
+ }
+ }
+ assert data == expected_data0
+
+ assert collected_data == [
+ {
+ "username": IsStr(),
+ "fork_roomid": fork_roomid0,
+ "fork_info": expected_data0[fork_roomid0],
+ "action": "create",
+ }
+ ]
+
+ resp = await rtc_create_fork_client(root_roomid, True, "my fork1", "is
awesome1")
+ data = json.loads(resp.body.decode("utf-8"))
+ fork_roomid1 = data["fork_roomid"]
+
+ resp = await rtc_get_forks_client(root_roomid)
+ data = json.loads(resp.body.decode("utf-8"))
+ expected_data1 = {
+ fork_roomid1: {
+ "root_roomid": root_roomid,
+ "synchronize": True,
+ "title": "my fork1",
+ "description": "is awesome1",
+ }
+ }
+ expected_data = dict(**expected_data0, **expected_data1)
+ assert data == expected_data
+
+ assert len(collected_data) == 2
+ assert collected_data[1] == {
+ "username": IsStr(),
+ "fork_roomid": fork_roomid1,
+ "fork_info": expected_data[fork_roomid1],
+ "action": "create",
+ }
+
+ fork_ydoc = YUnicode()
+ fork_connect_event = Event()
+
+ def _on_fork_change(topic: str, event: Any) -> None:
+ if topic == "source":
+ fork_connect_event.set()
+
+ fork_ydoc.observe(_on_fork_change)
+ fork_text = fork_ydoc.ydoc.get("source", type=Text)
+
+ async with await rtc_connect_fork_client(fork_roomid1) as ws,
WebsocketProvider(
+ fork_ydoc.ydoc, Websocket(ws, fork_roomid1)
+ ):
+ await fork_connect_event.wait()
+ root_text = root_ydoc.ydoc.get("source", type=Text)
+ root_text += ", World!"
+ await sleep(0.1)
+ assert str(fork_text) == "Hello, World!"
+ fork_text += " Hi!"
+ await sleep(0.1)
+
+ await sleep(0.1)
+ assert str(root_text) == "Hello, World!"
+
+ await rtc_delete_fork_client(fork_roomid0, True)
+ await sleep(0.1)
+ assert str(root_text) == "Hello, World!"
+ resp = await rtc_get_forks_client(root_roomid)
+ data = json.loads(resp.body.decode("utf-8"))
+ assert data == expected_data1
+ assert len(collected_data) == 3
+ assert collected_data[2] == {
+ "username": IsStr(),
+ "fork_roomid": fork_roomid0,
+ "fork_info": expected_data[fork_roomid0],
+ "action": "delete",
+ }
+
+ await rtc_delete_fork_client(fork_roomid1, True)
+ await sleep(0.1)
+ assert str(root_text) == "Hello, World! Hi!"
+ resp = await rtc_get_forks_client(root_roomid)
+ data = json.loads(resp.body.decode("utf-8"))
+ assert data == {}
+ assert len(collected_data) == 4
+ assert collected_data[3] == {
+ "username": IsStr(),
+ "fork_roomid": fork_roomid1,
+ "fork_info": expected_data[fork_roomid1],
+ "action": "delete",
+ }