55import json
66from logging import Logger
77from pathlib import Path
8- from typing import Any , Dict , Optional , Tuple
8+ from typing import Any , Dict , Optional , Tuple , Type
99
1010from jupyter_server .auth import authorized
1111from jupyter_server .base .handlers import APIHandler , JupyterHandler
1212from jupyter_server .utils import ensure_async
1313from jupyter_ydoc import ydocs as YDOCS # type: ignore
1414from tornado import web
1515from tornado .websocket import WebSocketHandler
16- from ypy_websocket import WebsocketServer , YMessageType , YRoom
17- from ypy_websocket .ystore import BaseYStore , SQLiteYStore , TempFileYStore , YDocNotFound
16+ from ypy_websocket .websocket_server import WebsocketServer , YRoom # type: ignore
17+ from ypy_websocket .ystore import BaseYStore , SQLiteYStore , TempFileYStore , YDocNotFound # type: ignore
18+ from ypy_websocket .yutils import YMessageType # type: ignore
1819
1920YFILE = YDOCS ["file" ]
2021
@@ -25,7 +26,7 @@ class JupyterTempFileYStore(TempFileYStore):
2526
2627def sqlite_ystore_factory (
2728 db_path : str = ".jupyter_ystore.db" , document_ttl : Optional [int ] = None
28- ) -> SQLiteYStore :
29+ ) -> Type [ SQLiteYStore ] :
2930 _db_path = db_path
3031 _document_ttl = document_ttl
3132
@@ -39,8 +40,6 @@ class JupyterSQLiteYStore(SQLiteYStore):
3940class DocumentRoom (YRoom ):
4041 """A Y room for a possibly stored document (e.g. a notebook)."""
4142
42- is_transient = False
43-
4443 def __init__ (self , type : str , ystore : BaseYStore , log : Optional [Logger ]):
4544 super ().__init__ (ready = False , ystore = ystore , log = log )
4645 self .type = type
@@ -52,8 +51,6 @@ def __init__(self, type: str, ystore: BaseYStore, log: Optional[Logger]):
5251class TransientRoom (YRoom ):
5352 """A Y room for sharing state (e.g. awareness)."""
5453
55- is_transient = True
56-
5754 def __init__ (self , log : Optional [Logger ]):
5855 super ().__init__ (log = log )
5956
@@ -135,6 +132,7 @@ async def __anext__(self):
135132
136133 def get_file_info (self ) -> Tuple [str , str , str ]:
137134 assert self .websocket_server is not None
135+ assert isinstance (self .room , DocumentRoom )
138136 room_name = self .websocket_server .get_room_name (self .room )
139137 file_format : str
140138 file_type : str
@@ -178,10 +176,10 @@ async def open(self, path):
178176 asyncio .create_task (self .websocket_server .serve (self ))
179177
180178 # cancel the deletion of the room if it was scheduled
181- if not self .room . is_transient and self .room .cleaner is not None :
179+ if isinstance ( self .room , DocumentRoom ) and self .room .cleaner is not None :
182180 self .room .cleaner .cancel ()
183181
184- if not self .room . is_transient and not self .room .ready :
182+ if isinstance ( self .room , DocumentRoom ) and not self .room .ready :
185183 file_format , file_type , file_path = self .get_file_info ()
186184 self .log .debug ("Opening Y document from disk: %s" , file_path )
187185 model = await ensure_async (
@@ -191,26 +189,30 @@ async def open(self, path):
191189 # check again if ready, because loading the file can be async
192190 if not self .room .ready :
193191 # try to apply Y updates from the YStore for this document
194- try :
195- await self .room .ystore .apply_updates (self .room .ydoc )
196- read_from_source = False
197- except YDocNotFound :
198- # YDoc not found in the YStore, create the document from the source file (no change history)
199- read_from_source = True
192+ read_from_source = True
193+ if self .room .ystore is not None :
194+ try :
195+ await self .room .ystore .apply_updates (self .room .ydoc )
196+ read_from_source = False
197+ except YDocNotFound :
198+ # YDoc not found in the YStore, create the document from the source file (no change history)
199+ pass
200200 if not read_from_source :
201201 # if YStore updates and source file are out-of-sync, resync updates with source
202202 if self .room .document .source != model ["content" ]:
203203 read_from_source = True
204204 if read_from_source :
205205 self .room .document .source = model ["content" ]
206- await self .room .ystore .encode_state_as_update (self .room .ydoc )
206+ if self .room .ystore :
207+ await self .room .ystore .encode_state_as_update (self .room .ydoc )
207208 self .room .document .dirty = False
208209 self .room .ready = True
209210 self .room .watcher = asyncio .create_task (self .watch_file ())
210211 # save the document when changed
211212 self .room .document .observe (self .on_document_change )
212213
213214 async def watch_file (self ):
215+ assert isinstance (self .room , DocumentRoom )
214216 poll_interval = self .settings ["collaborative_file_poll_interval" ]
215217 if not poll_interval :
216218 self .room .watcher = None
@@ -220,6 +222,7 @@ async def watch_file(self):
220222 await self .maybe_load_document ()
221223
222224 async def maybe_load_document (self ):
225+ assert isinstance (self .room , DocumentRoom )
223226 file_format , file_type , file_path = self .get_file_info ()
224227 async with self .lock :
225228 model = await ensure_async (
@@ -270,7 +273,7 @@ def on_message(self, message):
270273 # filter out message depending on changes
271274 if skip :
272275 self .log .debug (
273- "Filtered out Y message of type: %s" , YMessageType (message_type ).raw_str ()
276+ "Filtered out Y message of type: %s" , YMessageType (message_type ).name
274277 )
275278 return skip
276279 self ._message_queue .put_nowait (message )
@@ -279,12 +282,13 @@ def on_message(self, message):
279282 def on_close (self ) -> None :
280283 # stop serving this client
281284 self ._message_queue .put_nowait (b"" )
282- if not self .room . is_transient and self .room .clients == [self ]:
285+ if isinstance ( self .room , DocumentRoom ) and self .room .clients == [self ]:
283286 # no client in this room after we disconnect
284287 # keep the document for a while in case someone reconnects
285288 self .room .cleaner = asyncio .create_task (self .clean_room ())
286289
287290 async def clean_room (self ) -> None :
291+ assert isinstance (self .room , DocumentRoom )
288292 seconds = self .settings ["collaborative_document_cleanup_delay" ]
289293 if seconds is None :
290294 return
@@ -312,6 +316,7 @@ def on_document_change(self, event):
312316 self .saving_document = asyncio .create_task (self .maybe_save_document ())
313317
314318 async def maybe_save_document (self ):
319+ assert isinstance (self .room , DocumentRoom )
315320 seconds = self .settings ["collaborative_document_save_delay" ]
316321 if seconds is None :
317322 return
0 commit comments