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-06-23 14:56:33 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-jupyter-server-ydoc (Old) and /work/SRC/openSUSE:Factory/.python-jupyter-server-ydoc.new.7067 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-jupyter-server-ydoc" Mon Jun 23 14:56:33 2025 rev:3 rq:1287607 version:2.0.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-jupyter-server-ydoc/python-jupyter-server-ydoc.changes 2025-01-31 16:02:46.391947333 +0100 +++ /work/SRC/openSUSE:Factory/.python-jupyter-server-ydoc.new.7067/python-jupyter-server-ydoc.changes 2025-06-23 14:56:58.165069721 +0200 @@ -1,0 +2,6 @@ +Sat Jun 21 17:01:28 UTC 2025 - Ben Greiner <c...@bnavigator.de> + +- Update to 2.0.2 + * New subpackage version for jupyter-collaboration 4.0.2 + +------------------------------------------------------------------- Old: ---- jupyter_server_ydoc-1.1.0.tar.gz New: ---- jupyter_server_ydoc-2.0.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-jupyter-server-ydoc.spec ++++++ --- /var/tmp/diff_new_pack.YY8OFF/_old 2025-06-23 14:56:58.913101003 +0200 +++ /var/tmp/diff_new_pack.YY8OFF/_new 2025-06-23 14:56:58.917101171 +0200 @@ -25,9 +25,9 @@ %bcond_with test %endif -%define distversion 1.1 +%define distversion 2.0.2 Name: python-jupyter-server-ydoc%{psuffix} -Version: 1.1.0 +Version: 2.0.2 Release: 0 Summary: Jupyter server extension integrating collaborative shared models License: BSD-3-Clause ++++++ jupyter_server_ydoc-1.1.0.tar.gz -> jupyter_server_ydoc-2.0.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/PKG-INFO new/jupyter_server_ydoc-2.0.2/PKG-INFO --- old/jupyter_server_ydoc-1.1.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ -Metadata-Version: 2.3 +Metadata-Version: 2.4 Name: jupyter-server-ydoc -Version: 1.1.0 +Version: 2.0.2 Summary: jupyter-server extension integrating collaborative shared models. Author-email: Jupyter Development Team <jupy...@googlegroups.com> License: # Licensing terms @@ -62,6 +62,7 @@ # 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 @@ -75,9 +76,9 @@ Classifier: Programming Language :: Python :: 3.12 Requires-Python: >=3.8 Requires-Dist: jsonschema>=4.18.0 -Requires-Dist: jupyter-events>=0.10.0 +Requires-Dist: jupyter-events>=0.11.0 Requires-Dist: jupyter-server-fileid<1,>=0.7.0 -Requires-Dist: jupyter-server<3.0.0,>=2.11.1 +Requires-Dist: jupyter-server<3.0.0,>=2.15.0 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 @@ -88,7 +89,7 @@ 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: jupyter-server[test]>=2.15.0; extra == 'test' Requires-Dist: pytest-cov; extra == 'test' Requires-Dist: pytest>=7.0; extra == 'test' Description-Content-Type: text/markdown diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/_version.py new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/_version.py --- old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/_version.py 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/_version.py 2020-02-02 01:00:00.000000000 +0100 @@ -1 +1 @@ -__version__ = "1.1.0" +__version__ = "2.0.2" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/app.py new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/app.py --- old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/app.py 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/app.py 2020-02-02 01:00:00.000000000 +0100 @@ -105,7 +105,7 @@ page_config.setdefault("serverSideExecution", self.server_side_execution) # Set configurable parameters to YStore class - ystore_class = partial(self.ystore_class, config=self.config) + ystore_class: type[BaseYStore] = partial(self.ystore_class, config=self.config) # type:ignore[assignment] self.ywebsocket_server = JupyterWebsocketServer( rooms_ready=False, @@ -205,7 +205,7 @@ if copy: update = room.ydoc.get_update() - fork_ydoc = Doc() + fork_ydoc: Doc = Doc() fork_ydoc.apply_update(update) return YDOCS.get(room.file_type, YDOCS["file"])(fork_ydoc) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/events/awareness.yaml new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/events/awareness.yaml --- old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/events/awareness.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/events/awareness.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ "$id": https://schema.jupyter.org/jupyter_collaboration/awareness/v1 "$schema": "http://json-schema.org/draft-07/schema" -version: 1 +version: "1" title: Collaborative awareness events personal-data: true description: | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/events/fork.yaml new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/events/fork.yaml --- old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/events/fork.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/events/fork.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ "$id": https://schema.jupyter.org/jupyter_collaboration/fork/v1 "$schema": "http://json-schema.org/draft-07/schema" -version: 1 +version: "1" title: Collaborative fork events personal-data: true description: | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/events/session.yaml new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/events/session.yaml --- old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/events/session.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/events/session.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ "$id": https://schema.jupyter.org/jupyter_collaboration/session/v1 "$schema": "http://json-schema.org/draft-07/schema" -version: 1 +version: "1" title: Collaborative session events personal-data: true description: | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/handlers.py new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/handlers.py --- old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/handlers.py 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/handlers.py 2020-02-02 01:00:00.000000000 +0100 @@ -5,18 +5,17 @@ import asyncio import json -import time import uuid from logging import Logger -from typing import Any, Literal +from typing import Any from uuid import uuid4 from jupyter_server.auth import authorized from jupyter_server.base.handlers import APIHandler, JupyterHandler from jupyter_server.utils import ensure_async from jupyter_ydoc import ydocs as YDOCS -from pycrdt import Doc, UndoManager, write_var_uint -from pycrdt_websocket.websocket_server import YRoom +from pycrdt import Doc, UndoManager +from pycrdt_websocket.yroom import YRoom from pycrdt_websocket.ystore import BaseYStore from tornado import web from tornado.websocket import WebSocketHandler @@ -28,7 +27,6 @@ JUPYTER_COLLABORATION_EVENTS_URI, JUPYTER_COLLABORATION_FORK_EVENTS_URI, LogLevel, - MessageType, decode_file_path, encode_file_path, room_id_from_encoded_path, @@ -117,7 +115,10 @@ file = self._file_loaders[file_id] updates_file_path = f".{file_type}:{file_id}.y" - ystore = self._ystore_class(path=updates_file_path, log=self.log) + ystore = self._ystore_class( + path=updates_file_path, + log=self.log, # type:ignore[call-arg] + ) self.room = DocumentRoom( self._room_id, file_format, @@ -182,7 +183,7 @@ self._websocket_server = ywebsocket_server self._message_queue = asyncio.Queue() self._room_id = "" - self.room = None + self.room = None # type:ignore @property def path(self): @@ -219,7 +220,7 @@ raise web.HTTPError(403) return await super().get(*args, **kwargs) - async def open(self, room_id): + async def open(self, room_id: str) -> None: # type:ignore[override] """ On connection open. """ @@ -259,7 +260,7 @@ ) # Clean up the room and delete the file loader - if len(self.room.clients) == 0 or self.room.clients == [self]: + if len(self.room.clients) == 0 or self.room.clients == {self}: self._message_queue.put_nowait(b"") self._cleanup_delay = 0 await self._clean_room() @@ -290,28 +291,6 @@ """ On message receive. """ - message_type = message[0] - - if message_type == MessageType.CHAT: - msg = message[2:].decode("utf-8") - - user = self.current_user - data = json.dumps( - { - "sender": user.username, - "timestamp": time.time(), - "content": json.loads(msg), - } - ).encode("utf8") - - for client in self.room.clients: - if client != self: - task = asyncio.create_task( - client.send(bytes([MessageType.CHAT]) + write_var_uint(len(data)) + data) - ) - self._websocket_server.background_tasks.add(task) - task.add_done_callback(self._websocket_server.background_tasks.discard) - self._message_queue.put_nowait(message) self._websocket_server.ypatch_nb += 1 @@ -321,7 +300,7 @@ """ # stop serving this client self._message_queue.put_nowait(b"") - if isinstance(self.room, DocumentRoom) and self.room.clients == [self]: + if isinstance(self.room, DocumentRoom) and self.room.clients == {self}: # no client in this room after we disconnect # keep the document for a while in case someone reconnects self.log.info("Cleaning room: %s", self._room_id) @@ -386,9 +365,7 @@ self._emit(LogLevel.INFO, "clean", "Loader deleted.") del self._room_locks[self._room_id] - def _on_global_awareness_event( - self, topic: Literal["change", "update"], changes: tuple[dict[str, Any], Any] - ) -> None: + def _on_global_awareness_event(self, topic: str, changes: tuple[dict[str, Any], Any]) -> None: """ Update the users when the global awareness changes. @@ -489,7 +466,7 @@ try: room_id = room_id_from_encoded_path(encoded_path) room: YRoom = await self.ywebsocket_server.get_room(room_id) - fork_ydoc = Doc() + fork_ydoc: Doc = Doc() ydoc_factory = YDOCS.get(content_type) if ydoc_factory is None: @@ -505,7 +482,9 @@ FORK_DOCUMENTS[idx] = ydoc_factory(fork_ydoc) undo_manager: UndoManager = FORK_DOCUMENTS[idx].undo_manager - updates_and_timestamps = [(item[0], item[-1]) async for item in room.ystore.read()] + ystore = room.ystore + assert ystore + updates_and_timestamps = [(item[0], item[-1]) async for item in ystore.read()] result_timestamps = [] @@ -642,9 +621,14 @@ Optionally keeps the fork in sync with the root. """ fork_roomid = uuid4().hex - root_room = await self._websocket_server.get_room(root_roomid) + try: + root_room = await self._websocket_server.get_room(root_roomid) + except RoomNotFound: + self.set_status(404) + return self.finish({"code": 404, "error": "Root room not found"}) + update = root_room.ydoc.get_update() - fork_ydoc = Doc() + fork_ydoc: Doc = Doc() fork_ydoc.apply_update(update) model = self.get_json_body() synchronize = model.get("synchronize", False) @@ -676,7 +660,10 @@ """ Deletes a forked document, and optionally merges it back in the root document. """ - fork_info = FORK_ROOMS[fork_roomid] + fork_info = FORK_ROOMS.get(fork_roomid, None) + if fork_info is None: + self.set_status(404) + return self.finish({"code": 404, "error": "Fork room not found"}) root_roomid = fork_info["root_roomid"] del FORK_ROOMS[fork_roomid] if self.get_query_argument("merge") == "true": diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/loaders.py new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/loaders.py --- old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/loaders.py 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/loaders.py 2020-02-02 01:00:00.000000000 +0100 @@ -153,6 +153,9 @@ path, format=model["format"], type=model["type"], content=False ) ) + # Skip saving if file is not writable + if not m["writable"]: + return None if self.last_modified == m["last_modified"]: self._log.info("Saving file: %s", path) @@ -200,13 +203,28 @@ if self._poll_interval is None: return + consecutive_error_logs = 0 + max_consecutive_logs = 3 + suppression_logged = False + while True: try: await asyncio.sleep(self._poll_interval) try: await self.maybe_notify() + consecutive_error_logs = 0 + suppression_logged = False except Exception as e: - self._log.error(f"Error watching file: {self.path}\n{e!r}", exc_info=e) + if consecutive_error_logs < max_consecutive_logs: + self._log.error(f"Error watching file: {self.path}\n{e!r}", exc_info=e) + consecutive_error_logs += 1 + elif not suppression_logged: + self._log.warning( + "Too many errors while watching %s — suppressing further logs.", + self.path, + ) + suppression_logged = True + except asyncio.CancelledError: break diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/pytest_plugin.py new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/pytest_plugin.py --- old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/pytest_plugin.py 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/pytest_plugin.py 2020-02-02 01:00:00.000000000 +0100 @@ -240,7 +240,12 @@ def rtc_create_SQLite_store_factory(jp_serverapp): async def _inner(type: str, path: str, content: str) -> DocumentRoom: - db = SQLiteYStore(path=f"{type}:{path}", config=jp_serverapp.config) + db = SQLiteYStore( + path=f"{type}:{path}", + # `SQLiteYStore` here is a subclass of booth `LoggingConfigurable` + # and `pycrdt_websocket.ystore.SQLiteYStore`, but mypy gets lost: + config=jp_serverapp.config, # type:ignore[call-arg] + ) _ = create_task(db.start()) await db.started.wait() @@ -271,13 +276,16 @@ last_modified: datetime | None = None, save_delay: float | None = None, store: SQLiteYStore | None = None, + writable: bool = False, ) -> tuple[FakeContentsManager, FileLoader, DocumentRoom]: paths = {id: path} if last_modified is None: - cm = FakeContentsManager({"content": content}) + cm = FakeContentsManager({"content": content, "writable": writable}) else: - cm = FakeContentsManager({"last_modified": datetime.now(), "content": content}) + cm = FakeContentsManager( + {"last_modified": datetime.now(), "content": content, "writable": writable} + ) loader = FileLoader( id, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/rooms.py new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/rooms.py --- old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/rooms.py 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/rooms.py 2020-02-02 01:00:00.000000000 +0100 @@ -9,7 +9,7 @@ from jupyter_events import EventLogger from jupyter_ydoc import ydocs as YDOCS -from pycrdt_websocket.websocket_server import YRoom +from pycrdt_websocket.yroom import YRoom from pycrdt_websocket.ystore import BaseYStore, YDocNotFound from .loaders import FileLoader @@ -103,7 +103,7 @@ It is important to set the ready property in the parent class (`self.ready = True`), this setter will subscribe for updates on the shared document. """ - if self.ready: # type: ignore[has-type] + if self.ready: return self.log.info("Initializing room %s", self._room_id) @@ -285,9 +285,9 @@ "content": self._document.source, } ) - async with self._update_lock: - self._document.dirty = False - if saved_model: + if saved_model: + async with self._update_lock: + self._document.dirty = False self._document.hash = saved_model["hash"] self._emit(LogLevel.INFO, "save", "Content saved.") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/websocketserver.py new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/websocketserver.py --- old/jupyter_server_ydoc-1.1.0/jupyter_server_ydoc/websocketserver.py 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/jupyter_server_ydoc/websocketserver.py 2020-02-02 01:00:00.000000000 +0100 @@ -7,9 +7,10 @@ from logging import Logger from typing import Any, Callable -from pycrdt_websocket.websocket_server import WebsocketServer, YRoom +from pycrdt_websocket.websocket import Websocket +from pycrdt_websocket.websocket_server import WebsocketServer +from pycrdt_websocket.yroom import YRoom from pycrdt_websocket.ystore import BaseYStore -from tornado.websocket import WebSocketHandler class RoomNotFound(LookupError): @@ -38,7 +39,7 @@ def __init__( self, - ystore_class: BaseYStore, + ystore_class: type[BaseYStore], rooms_ready: bool = True, auto_clean_rooms: bool = True, exception_handler: Callable[[Exception, Logger], bool] | None = None, @@ -132,7 +133,7 @@ await self.start_room(room) return room - async def serve(self, websocket: WebSocketHandler) -> None: + async def serve(self, websocket: Websocket) -> None: # start monitoring here as the event loop is not yet available when initializing the object if self.monitor_task is None: self.monitor_task = asyncio.create_task(self._monitor()) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/pyproject.toml new/jupyter_server_ydoc-2.0.2/pyproject.toml --- old/jupyter_server_ydoc-1.1.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 @@ -28,11 +28,11 @@ { name = "Jupyter Development Team", email = "jupy...@googlegroups.com" }, ] dependencies = [ - "jupyter_server>=2.11.1,<3.0.0", + "jupyter_server>=2.15.0,<3.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", + "jupyter_events>=0.11.0", "jupyter_server_fileid>=0.7.0,<1", "jsonschema>=4.18.0" ] @@ -42,7 +42,7 @@ test = [ "coverage", "dirty-equals", - "jupyter_server[test]>=2.4.0", + "jupyter_server[test]>=2.15.0", "jupyter_server_fileid[test]", "pytest>=7.0", "pytest-cov", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/jupyter_server_ydoc-1.1.0/tests/test_rooms.py new/jupyter_server_ydoc-2.0.2/tests/test_rooms.py --- old/jupyter_server_ydoc-1.1.0/tests/test_rooms.py 2020-02-02 01:00:00.000000000 +0100 +++ new/jupyter_server_ydoc-2.0.2/tests/test_rooms.py 2020-02-02 01:00:00.000000000 +0100 @@ -51,7 +51,9 @@ rtc_create_mock_document_room, ): content = "test" - cm, _, room = rtc_create_mock_document_room("test-id", "test.txt", content, save_delay=0.01) + cm, _, room = rtc_create_mock_document_room( + "test-id", "test.txt", content, save_delay=0.01, writable=True + ) await room.initialize() room._document.source = "Test 2"