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",
+        }

Reply via email to