1717
1818if TYPE_CHECKING :
1919 import logging
20- from typing import Coroutine , Literal , Tuple , Any
20+ from typing import Callable , Coroutine , Literal , Tuple , Any
2121 from .yroom_manager import YRoomManager
22- from jupyter_server_fileid .manager import BaseFileIdManager
22+ from jupyter_server_fileid .manager import BaseFileIdManager # type: ignore
2323 from jupyter_server .services .contents .manager import ContentsManager
24- from pycrdt import TransactionEvent
24+ from pycrdt import TransactionEvent , Subscription
2525 from ..outputs .manager import OutputsManager
2626
2727class YRoom (LoggingConfigurable ):
@@ -136,7 +136,7 @@ class YRoom(LoggingConfigurable):
136136 documentation for more info.
137137 """
138138
139- _jupyter_ydoc_observers : dict [str , callable [[str , Any ], Any ]]
139+ _jupyter_ydoc_observers : dict [str , Callable [[str , Any ], Any ]]
140140 """
141141 Dictionary of JupyterYDoc observers added by consumers of this room.
142142
@@ -167,10 +167,10 @@ class YRoom(LoggingConfigurable):
167167 `self._message_queue.put_nowait(None)`.
168168 """
169169
170- _awareness_subscription : pycrdt . Subscription
170+ _awareness_subscription : str
171171 """Subscription to awareness changes."""
172172
173- _ydoc_subscription : pycrdt . Subscription
173+ _ydoc_subscription : Subscription
174174 """Subscription to YDoc changes."""
175175
176176 _stopped : bool
@@ -346,6 +346,8 @@ async def get_jupyter_ydoc(self) -> YBaseDoc:
346346 message = "There is no Jupyter ydoc for global awareness scenario"
347347 self .log .error (message )
348348 raise Exception (message )
349+ if self ._jupyter_ydoc is None :
350+ raise RuntimeError ("Jupyter YDoc is not available" )
349351 if self .file_api :
350352 await self .file_api .until_content_loaded
351353 return self ._jupyter_ydoc
@@ -428,7 +430,7 @@ def handle_message(self, client_id: str, message: bytes) -> None:
428430
429431 # Determine message type & subtype from header
430432 message_type = message [0 ]
431- sync_message_subtype = "*"
433+ sync_message_subtype = - 1 # invalid sentinel value
432434 # message subtypes only exist on sync messages, hence this condition
433435 if message_type == YMessageType .SYNC and len (message ) >= 2 :
434436 sync_message_subtype = message [1 ]
@@ -585,7 +587,7 @@ def _on_ydoc_update(self, event: TransactionEvent) -> None:
585587 self ._broadcast_message (message , message_type = "SyncUpdate" )
586588
587589
588- def observe_jupyter_ydoc (self , observer : callable [[str , Any ], Any ]) -> str :
590+ def observe_jupyter_ydoc (self , observer : Callable [[str , Any ], Any ]) -> str :
589591 """
590592 Adds an observer callback to the JupyterYDoc that fires on change.
591593 The callback should accept 2 arguments:
@@ -604,8 +606,9 @@ def observe_jupyter_ydoc(self, observer: callable[[str, Any], Any]) -> str:
604606 Returns an `observer_id: str` that can be passed to
605607 `unobserve_jupyter_ydoc()` to remove the observer.
606608 """
607- observer_id = uuid .uuid4 ()
609+ observer_id = str ( uuid .uuid4 () )
608610 self ._jupyter_ydoc_observers [observer_id ] = observer
611+ return observer_id
609612
610613
611614 def unobserve_jupyter_ydoc (self , observer_id : str ):
@@ -745,7 +748,10 @@ def _on_awareness_update(self, type: str, changes: tuple[dict[str, Any], Any]) -
745748 self .log .debug (f"awareness update, updated_clients={ updated_clients } " )
746749 state = self ._awareness .encode_awareness_update (updated_clients )
747750 message = pycrdt .create_awareness_message (state )
748- self .log .debug (f"awareness update, message={ message } " )
751+ # !r ensures binary messages show as `b'...'` instead of being decoded
752+ # into jargon in log statements.
753+ # https://docs.python.org/3/library/string.html#format-string-syntax
754+ self .log .debug (f"awareness update, message={ message !r} " )
749755 self ._broadcast_message (message , "AwarenessUpdate" )
750756
751757
@@ -827,8 +833,11 @@ def stop(self, close_code: int = 1001, immediately: bool = False) -> None:
827833 self ._message_queue .get_nowait ()
828834 self ._message_queue .task_done ()
829835 else :
830- client_id , message = self ._message_queue .get_nowait ()
831- self .handle_message (client_id , message )
836+ queue_item = self ._message_queue .get_nowait ()
837+ if queue_item is not None :
838+ client_id , message = queue_item
839+ self .handle_message (client_id , message )
840+ self ._message_queue .task_done ()
832841
833842 # Stop the `_process_message_queue` task by enqueueing `None`
834843 self ._message_queue .put_nowait (None )
@@ -924,8 +933,9 @@ def restart(self, close_code: int = 1001, immediately: bool = False) -> None:
924933 self .clients .restart ()
925934
926935 # Restart `YRoomFileAPI` & reload the document
927- self .file_api .restart ()
928- self .file_api .load_content_into (self ._jupyter_ydoc )
936+ if self .file_api is not None and self ._jupyter_ydoc is not None :
937+ self .file_api .restart ()
938+ self .file_api .load_content_into (self ._jupyter_ydoc )
929939
930940 # Restart `_process_message_queue()` task
931941 asyncio .create_task (self ._process_message_queue ())
@@ -952,16 +962,16 @@ def should_ignore_state_update(event: pycrdt.MapEvent) -> bool:
952962 # `False` immediately if:
953963 # - a key was updated to a value different from the previous value
954964 # - a key was added with a value different from the previous value
955- for key in event . keys .keys ():
956- update_info = event . keys [key ]
965+ for key in getattr ( event , ' keys' , {}) .keys ():
966+ update_info = getattr ( event , ' keys' , {}) [key ]
957967 action = update_info .get ('action' , None )
958968 if action == 'update' :
959969 old_value = update_info .get ('oldValue' , None )
960970 new_value = update_info .get ('newValue' , None )
961971 if old_value != new_value :
962972 return False
963973 elif action == "add" :
964- old_value = event . target .get (key , None )
974+ old_value = getattr ( event , ' target' , {}) .get (key , None )
965975 new_value = update_info .get ('newValue' , None )
966976 if old_value != new_value :
967977 return False
0 commit comments