1111import io
1212import pathlib
1313from pathlib import Path
14- import time
1514from typing import ClassVar , Dict , Final , List , Optional , Tuple , Union
1615
1716try : # drop this clause when we drop python 3.9
3231
3332from .utils .exceptions import WidgetReducedError , WidgetNotReadyError
3433from .utils ._coordinate_parser import _parse_coordinate_string
34+ from .utils .call_store import CallStore
3535from .elements .error_shape import (
3636 CircleError ,
3737 EllipseError ,
8787def widget_should_be_loaded (function : Callable ) -> Callable :
8888 """Check if the widget is ready to execute a function.
8989
90+ Store the call for a later execution.
91+
9092 Parameters
9193 ----------
9294 function : Callable
@@ -103,6 +105,9 @@ def widget_should_be_loaded(function: Callable) -> Callable:
103105 def wrapper (self : Any , * args : Any , ** kwargs : Any ) -> Any :
104106 """Check if the widget is ready to execute a function.
105107
108+ If not, do not exit the call and store it so that it is
109+ called later when the widget is ready.
110+
106111 Parameters
107112 ----------
108113 self : any
@@ -119,12 +124,9 @@ def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
119124
120125 """
121126 if not getattr (self , "_is_loaded" , False ):
122- # this is an arbitrary waiting time
123- # it should correspond to the downloading time of the npm package
124- duration = 0.1
125- time .sleep (duration )
126- # set ready to True to avoid waiting twice
127- self ._is_loaded = True
127+ self ._call_store .add (function , self , * args , ** kwargs )
128+ return None
129+
128130 return function (self , * args , ** kwargs )
129131
130132 return wrapper
@@ -140,6 +142,8 @@ class Aladin(anywidget.AnyWidget):
140142 _esm : Final = pathlib .Path (__file__ ).parent / "static" / "widget.js"
141143 _css : Final = pathlib .Path (__file__ ).parent / "static" / "widget.css"
142144
145+ _call_store = CallStore ()
146+
143147 # Options for the view initialization
144148 _init_options = traitlets .Dict ().tag (sync = True )
145149 _height = Int (400 ).tag (sync = True , init_option = True )
@@ -246,6 +250,17 @@ def __init__(self, *args: any, **init_options: any) -> None:
246250 self ._init_options = init_options
247251 self .on_msg (self ._handle_custom_message )
248252
253+ def on_load_change (change : traitlets .Dict ) -> None :
254+ if change ["new" ]:
255+ self ._call_store .run_all ()
256+
257+ # There can be a race condition if the JS is already load
258+ # leading to _is_loaded = True before observing it.
259+ # If this is the case there should not be any problem because no python commands
260+ # are expected to be called on an object before the object itself
261+ # is instanciated.
262+ self .observe (on_load_change , names = "_is_loaded" )
263+
249264 def _handle_custom_message (self , _ : any , message : dict , buffers : any ) -> None :
250265 event_type = message ["event_type" ]
251266 if (
0 commit comments