1- import datetime
21import base64
2+ import collections
3+ import datetime
34from textwrap import dedent
45
56from async_generator import asynccontextmanager
2223 CellExecutionError
2324)
2425from .util import run_sync , ensure_async
26+ from .output_widget import OutputWidget
2527
2628
2729def timestamp ():
@@ -307,6 +309,11 @@ def reset_execution_trackers(self):
307309 self ._display_id_map = {}
308310 self .widget_state = {}
309311 self .widget_buffers = {}
312+ # maps to list of hooks, where the last is used, this is used
313+ # to support nested use of output widgets.
314+ self .output_hook_stack = collections .defaultdict (list )
315+ # our front-end mimicing Output widgets
316+ self .output_widget_objects = {}
310317
311318 def start_kernel_manager (self ):
312319 """Creates a new kernel manager.
@@ -787,6 +794,14 @@ def process_message(self, msg, cell, cell_index):
787794 def output (self , outs , msg , display_id , cell_index ):
788795 msg_type = msg ['msg_type' ]
789796
797+ parent_msg_id = msg ['parent_header' ].get ('msg_id' )
798+ if self .output_hook_stack [parent_msg_id ]:
799+ # if we have a hook registered, it will overrride our
800+ # default output behaviour (e.g. OutputWidget)
801+ hook = self .output_hook_stack [parent_msg_id ][- 1 ]
802+ hook .output (outs , msg , display_id , cell_index )
803+ return
804+
790805 try :
791806 out = output_from_msg (msg )
792807 except ValueError :
@@ -812,6 +827,15 @@ def output(self, outs, msg, display_id, cell_index):
812827
813828 def clear_output (self , outs , msg , cell_index ):
814829 content = msg ['content' ]
830+
831+ parent_msg_id = msg ['parent_header' ].get ('msg_id' )
832+ if self .output_hook_stack [parent_msg_id ]:
833+ # if we have a hook registered, it will overrride our
834+ # default clear_output behaviour (e.g. OutputWidget)
835+ hook = self .output_hook_stack [parent_msg_id ][- 1 ]
836+ hook .clear_output (outs , msg , cell_index )
837+ return
838+
815839 if content .get ('wait' ):
816840 self .log .debug ('Wait to clear output' )
817841 self .clear_before_next_output = True
@@ -832,6 +856,24 @@ def handle_comm_msg(self, outs, msg, cell_index):
832856 self .widget_state .setdefault (content ['comm_id' ], {}).update (data ['state' ])
833857 if 'buffer_paths' in data and data ['buffer_paths' ]:
834858 self .widget_buffers [content ['comm_id' ]] = self ._get_buffer_data (msg )
859+ # There are cases where we need to mimic a frontend, to get similar behaviour as
860+ # when using the Output widget from Jupyter lab/notebook
861+ if msg ['msg_type' ] == 'comm_open' and msg ['content' ].get ('target_name' ) == 'jupyter.widget' :
862+ content = msg ['content' ]
863+ data = content ['data' ]
864+ state = data ['state' ]
865+ comm_id = msg ['content' ]['comm_id' ]
866+ if state ['_model_module' ] == '@jupyter-widgets/output' and \
867+ state ['_model_name' ] == 'OutputModel' :
868+ self .output_widget_objects [comm_id ] = OutputWidget (comm_id , state , self .kc , self )
869+ elif msg ['msg_type' ] == 'comm_msg' :
870+ content = msg ['content' ]
871+ data = content ['data' ]
872+ if 'state' in data :
873+ state = data ['state' ]
874+ comm_id = msg ['content' ]['comm_id' ]
875+ if comm_id in self .output_widget_objects :
876+ self .output_widget_objects [comm_id ].set_state (state )
835877
836878 def _serialize_widget_state (self , state ):
837879 """Serialize a widget state, following format in @jupyter-widgets/schema."""
@@ -856,6 +898,22 @@ def _get_buffer_data(self, msg):
856898 )
857899 return encoded_buffers
858900
901+ def register_output_hook (self , msg_id , hook ):
902+ """Registers an override object that handles output/clear_output instead.
903+
904+ Multiple hooks can be registered, where the last one will be used (stack based)
905+ """
906+ # mimics
907+ # https://jupyterlab.github.io/jupyterlab/services/interfaces/kernel.ikernelconnection.html#registermessagehook
908+ self .output_hook_stack [msg_id ].append (hook )
909+
910+ def remove_output_hook (self , msg_id , hook ):
911+ """Unregisters an override object that handles output/clear_output instead"""
912+ # mimics
913+ # https://jupyterlab.github.io/jupyterlab/services/interfaces/kernel.ikernelconnection.html#removemessagehook
914+ removed_hook = self .output_hook_stack [msg_id ].pop ()
915+ assert removed_hook == hook
916+
859917
860918def execute (nb , cwd = None , km = None , ** kwargs ):
861919 """Execute a notebook's code, updating outputs within the notebook object.
0 commit comments