88import textwrap
99
1010from contextlib import contextmanager
11- from functools import partial
1211from types import<
10BC0
/span> ModuleType
1312from typing import IO , Any , Callable
1413
2322from bokeh .io .doc import patch_curdoc
2423from bokeh .util .dependencies import import_required
2524
25+ from .state import state
26+
2627log = logging .getLogger ('panel.io.handlers' )
2728
2829CELL_DISPLAY = []
@@ -188,8 +189,8 @@ def __init__(self, *, filename: PathLike, argv: list[str] = [], package: ModuleT
188189 filename (str) : a path to a Jupyter notebook (".ipynb") file
189190
190191 '''
191- self ._stale = True
192192 super ().__init__ (source = self ._parse (filename ), filename = filename )
193+ self ._stale = False
193194
194195 def _parse (self , filename ):
195196 nbformat = import_required ('nbformat' , 'The Bokeh notebook application handler requires Jupyter Notebook to be installed.' )
@@ -256,8 +257,7 @@ def __call__(self, nb, resources):
256257 md = '' .join (cell ['source' ])
257258 code .append (f'_pn__state._cell_outputs[{ cell_id !r} ].append("""{ md } """)' )
258259 code = '\n ' .join (code )
259- nbformat .write (nb , filename )
260- self ._stale = False
260+ self ._nb = nb
261261 return code
262262
263263 def modify_document (self , doc : Document ) -> None :
@@ -272,6 +272,7 @@ def modify_document(self, doc: Document) -> None:
272272 source = self ._parse (path )
273273 nodes = ast .parse (source , os .fspath (path ))
274274 self ._runner ._code = compile (nodes , filename = path , mode = 'exec' , dont_inherit = True )
275+ self ._stale = False
275276
276277 module = self ._runner .new_module ()
277278
@@ -296,6 +297,7 @@ def modify_document(self, doc: Document) -> None:
296297 self ._runner .run (module , self ._make_post_doc_check (doc ))
297298
298299 if doc .roots :
300+ state ._cell_outputs .clear ()
299301 return
300302
301303 config .template = 'editable'
@@ -329,19 +331,21 @@ def modify_document(self, doc: Document) -> None:
329331
330332 # Set up state
331333 state .template .layout = ordered
334+ state .template .param .watch (self ._update_position_metadata , 'layout' )
332335 state ._cell_outputs .clear ()
333- # Note: Big memory leak
334- state .template .param .watch (
335- partial (self ._update_position_metadata , outputs ), 'layout'
336- )
337336
338- def _update_position_metadata (self , outputs , event ):
337+ def _update_position_metadata (self , event ):
338+ """
339+ Maps EditableTemplate update events to cells in the original
340+ notebook and then overwrites notebook metadata with updated
341+ layout information.
342+ """
339343 import nbformat
340- nb = nbformat .read (self ._runner ._path , nbformat .NO_CONVERT )
344+ nb = self ._nb
345+ doc = event .obj ._documents [- 1 ]
346+ outputs = state ._session_outputs [doc ]
341347 cell_ids = {}
342348 for cell in nb ['cells' ]:
343- if 'id' not in cell :
344- continue
345349 if cell ['id' ] in outputs :
346350 out = outputs [cell ['id' ]]
347351 cell_ids [id (out )] = cell ['id' ]
@@ -356,7 +360,7 @@ def _update_position_metadata(self, outputs, event):
356360
357361
358362def build_single_handler_application (path , argv = None ):
359- if not os .path .isfile (path ) and not (path .endswith (".md" ) or path .endswith (".ipynb" )):
363+ if not os .path .isfile (path ) or not (path .endswith (".md" ) or path .endswith (".ipynb" )):
360364 return _build_application (path , argv )
361365
362366 from .server import Application
0 commit comments