1717# The default minor version of the notebook format.
1818NBFORMAT_MINOR_VERSION = 5
1919
20+ _CELL_KEY_TYPE_MAP = {"metadata" : Map , "source" : Text , "outputs" : Array }
21+
2022
2123class YNotebook (YBaseDoc ):
2224 """
@@ -249,7 +251,7 @@ def set(self, value: dict) -> None:
249251 "id" : str (uuid4 ()),
250252 }
251253 ]
252- old_ycells_by_id = {ycell ["id" ]: ycell for ycell in self ._ycells }
254+ old_ycells_by_id : dict [ str , Map ] = {ycell ["id" ]: ycell for ycell in self ._ycells }
253255
254256 with self ._ydoc .transaction ():
255257 new_cell_list : list [dict ] = []
@@ -260,7 +262,55 @@ def set(self, value: dict) -> None:
260262 cell_id = new_cell .get ("id" )
261263 if cell_id and (old_ycell := old_ycells_by_id .get (cell_id )):
262264 old_cell = self ._cell_to_py (old_ycell )
263- if old_cell == new_cell :
265+ updated_granularly = True
266+ if old_cell != new_cell :
267+ # attempt to update cell granularly
268+ old_keys = set (old_cell .keys ())
269+ new_keys = set (new_cell .keys ())
270+
271+ shared_keys = old_keys & new_keys
272+ removed_keys = old_keys - new_keys
273+ added_keys = new_keys - old_keys
274+
275+ for key in shared_keys :
276+ if old_cell [key ] != new_cell [key ]:
277+ if key == "output" and value :
278+ # outputs require complex handling - some have Text type nested;
279+ # for now skip creating them; clearing all outputs is fine
280+ updated_granularly = False
281+
282+ if key in _CELL_KEY_TYPE_MAP :
283+ kind = _CELL_KEY_TYPE_MAP [key ]
284+ value = new_cell [key ]
285+ if kind == Text :
286+ old : Text = old_ycell [key ]
287+ old .clear ()
288+ old .insert (0 , value )
289+ elif kind == Array :
290+ old : Array = old_ycell [key ]
291+ old .clear ()
292+ old .extend (value )
293+ elif kind == Map :
294+ old : Map = old_ycell [key ]
295+ old .clear ()
296+ for k , v in value .items ():
297+ old [k ] = v
298+ else :
299+ old_ycell [key ] = new_cell [key ]
300+
301+ for key in removed_keys :
302+ del old_ycell [key ]
303+
304+ for key in added_keys :
305+ if key in _CELL_KEY_TYPE_MAP :
306+ # we hard-reload cells when keys that require nested types get added
307+ # to allow the frontend to connect observers; this could be changed
308+ # in the future, once frontends learn how to observe all changes
309+ updated_granularly = False
310+ else :
311+ old_ycell [key ] = new_cell [key ]
312+
313+ if updated_granularly :
264314 new_cell_list .append (old_cell )
265315 retained_cells .add (cell_id )
266316 continue
0 commit comments