88"""
99
1010from collections .abc import Mapping
11+ import threading
1112
1213is_pyodide = False
1314is_pyodide_main_thread = False
@@ -127,16 +128,37 @@ def _serialize_jsproxy(link, value):
127128
128129
129130def execute_when_init (func ):
131+ """Register a callback to run once the JS side is ready.
132+
133+ If the platform has already been initialized, the callback is executed
134+ immediately. Otherwise it is queued and executed from ``init`` once the
135+ websocket connection has been established and ``js`` is set.
136+ """
137+
130138 if js is not None :
131139 func (js )
132140 else :
133141 _funcs_after_init .append (func )
134142
135143
136- def init (before_wait_for_connection = None ):
144+ def init (before_wait_for_connection = None , block_on_connection : bool = True ):
145+ """Initialize the websocket link to the browser.
146+
147+ In the default (classic Jupyter) mode, this blocks until the browser has
148+ connected via websocket so that ``js`` is ready to use.
149+
150+ In environments like VS Code notebooks, outputs are typically only
151+ processed once the cell has finished executing. In that situation calling
152+ ``init`` with ``block_on_connection=False`` avoids a deadlock by moving the
153+ blocking ``wait_for_connection`` part to a background thread. Code that
154+ depends on ``js`` should use :func:`execute_when_init` so it runs once the
155+ connection is ready.
156+ """
157+
137158 global js , create_proxy , destroy_proxy , websocket_server , link
138159 if is_pyodide or js is not None :
139160 return
161+
140162 websocket_server = WebsocketLinkServer ()
141163 create_proxy = websocket_server .create_proxy
142164 destroy_proxy = websocket_server .destroy_proxy
@@ -147,19 +169,30 @@ def init(before_wait_for_connection=None):
147169 if before_wait_for_connection :
148170 before_wait_for_connection (websocket_server )
149171
150- websocket_server .wait_for_connection ()
151- js = websocket_server .get (None , None )
152-
153172 from .link .base import LinkBase
154173 from .webgpu_api import BaseWebGPUHandle , BaseWebGPUObject
155174
156- LinkBase .register_serializer (BaseWebGPUHandle , lambda _ , v : v .handle )
157- LinkBase .register_serializer (BaseWebGPUObject , lambda _ , v : v .__dict__ or None )
175+ def _finish_init ():
176+ websocket_server .wait_for_connection ()
177+ js_local = websocket_server .get (None , None )
158178
159- websocket_server ._start_handling_messages .set ()
160- for func in _funcs_after_init :
161- func (js )
162- _funcs_after_init .clear ()
179+ LinkBase .register_serializer (BaseWebGPUHandle , lambda _ , v : v .handle )
180+ LinkBase .register_serializer (
181+ BaseWebGPUObject , lambda _ , v : v .__dict__ or None
182+ )
183+
184+ # Publish js and run any deferred callbacks.
185+ globals ()["js" ] = js_local
186+ websocket_server ._start_handling_messages .set ()
187+ for func in _funcs_after_init :
188+ func (js_local )
189+ _funcs_after_init .clear ()
190+
191+ if block_on_connection :
192+ _finish_init ()
193+ else :
194+ thread = threading .Thread (target = _finish_init , daemon = True )
195+ thread .start ()
163196
164197
165198def init_pyodide (link_ ):
0 commit comments