@@ -144,6 +144,14 @@ class YRoom(LoggingConfigurable):
144144 `unobserve_jupyter_ydoc()`.
145145 """
146146
147+ # TODO: define a dataclass for this to ensure values are type-safe
148+ _on_reset_callbacks : dict [Literal ['awareness' , 'ydoc' , 'jupyter_ydoc' ], list [Callable [[Any ], Any ]]]
149+ """
150+ Dictionary that stores all `on_reset` callbacks passed to `get_awareness()`,
151+ `get_jupyter_ydoc()`, or `get_ydoc()`. These are stored in lists under the
152+ 'awareness', 'ydoc' and 'jupyter_ydoc' keys respectively.
153+ """
154+
147155 _ydoc : pycrdt .Doc
148156 """
149157 The `YDoc` for this room's document. See `get_ydoc()` documentation for more
@@ -197,6 +205,11 @@ def __init__(self, *args, **kwargs):
197205
198206 # Initialize instance attributes
199207 self ._jupyter_ydoc_observers = {}
208+ self ._on_reset_callbacks = {
209+ "awareness" : [],
210+ "jupyter_ydoc" : [],
211+ "ydoc" : [],
212+ }
200213 self ._stopped = False
201214 self ._updated = False
202215 self ._save_task = None
@@ -336,11 +349,16 @@ def clients(self) -> YjsClientGroup:
336349 return self ._client_group
337350
338351
339- async def get_jupyter_ydoc (self ) -> YBaseDoc :
352+ async def get_jupyter_ydoc (self , on_reset : Callable [[ YBaseDoc ], Any ] | None = None ) -> YBaseDoc :
340353 """
341- Returns a reference to the room's JupyterYDoc
354+ Returns a reference to the room's Jupyter YDoc
342355 (`jupyter_ydoc.ybasedoc.YBaseDoc`) after waiting for its content to be
343356 loaded from the ContentsManager.
357+
358+ This method also accepts an `on_reset` callback, which should take a
359+ Jupyter YDoc as an argument. This callback is run with the new Jupyter
360+ YDoc whenever the YDoc is reset, e.g. in response to an out-of-band
361+ change.
344362 """
345363 if self .room_id == "JupyterLab:globalAwareness" :
346364 message = "There is no Jupyter ydoc for global awareness scenario"
@@ -350,23 +368,39 @@ async def get_jupyter_ydoc(self) -> YBaseDoc:
350368 raise RuntimeError ("Jupyter YDoc is not available" )
351369 if self .file_api :
352370 await self .file_api .until_content_loaded
371+ if on_reset :
372+ self ._on_reset_callbacks ['jupyter_ydoc' ].append (on_reset )
373+
353374 return self ._jupyter_ydoc
354375
355376
356- async def get_ydoc (self ) -> pycrdt .Doc :
377+ async def get_ydoc (self , on_reset : Callable [[ pycrdt . Doc ], Any ] | None = None ) -> pycrdt .Doc :
357378 """
358379 Returns a reference to the room's YDoc (`pycrdt.Doc`) after
359380 waiting for its content to be loaded from the ContentsManager.
381+
382+ This method also accepts an `on_reset` callback, which should take a
383+ YDoc as an argument. This callback is run with the new YDoc object
384+ whenever the YDoc is reset, e.g. in response to an out-of-band change.
360385 """
361386 if self .file_api :
362387 await self .file_api .until_content_loaded
388+ if on_reset :
389+ self ._on_reset_callbacks ['ydoc' ].append (on_reset )
363390 return self ._ydoc
364391
365392
366- def get_awareness (self ) -> pycrdt .Awareness :
393+ def get_awareness (self , on_reset : Callable [[ pycrdt . Awareness ], Any ] | None = None ) -> pycrdt .Awareness :
367394 """
368395 Returns a reference to the room's awareness (`pycrdt.Awareness`).
396+
397+ This method also accepts an `on_reset` callback, which should take an
398+ Awareness object as an argument. This callback is run with the new
399+ Awareness object whenever the YDoc is reset, e.g. in response to an
400+ out-of-band change.
369401 """
402+ if on_reset :
403+ self ._on_reset_callbacks ['awareness' ].append (on_reset )
370404 return self ._awareness
371405
372406 def get_cell_execution_states (self ) -> dict :
@@ -914,13 +948,34 @@ def _reset_ydoc(self) -> None:
914948 """
915949 Deletes and re-initializes the YDoc, awareness, and JupyterYDoc. This
916950 frees the memory occupied by their histories.
951+
952+ This runs all `on_reset` callbacks previously passed to `get_ydoc()`,
953+ `get_jupyter_ydoc()`, or `get_awareness()`.
917954 """
918955 self ._ydoc = self ._init_ydoc ()
919956 self ._awareness = self ._init_awareness (ydoc = self ._ydoc )
920957 self ._jupyter_ydoc = self ._init_jupyter_ydoc (
921958 ydoc = self ._ydoc ,
922959 awareness = self ._awareness
923960 )
961+
962+ # Run callbacks stored in `self._on_reset_callbacks`.
963+ objects_by_type = {
964+ "awareness" : self ._awareness ,
965+ "jupyter_ydoc" : self ._jupyter_ydoc ,
966+ "ydoc" : self ._ydoc ,
967+ }
968+ for obj_type , obj in objects_by_type .items ():
969+ # This is type-safe, but requires a mypy hint because it cannot
970+ # infer that `obj_type` only takes 3 values.
971+ for on_reset in self ._on_reset_callbacks [obj_type ]: # type: ignore
972+ try :
973+ result = on_reset (obj )
974+ if asyncio .iscoroutine (result ):
975+ asyncio .create_task (result )
976+ except Exception :
977+ self .log .exception (f"Exception raised by '{ obj_type } ' on_reset() callback:" )
978+ continue
924979
925980 @property
926981 def stopped (self ) -> bool :
0 commit comments