Skip to content

Commit a474d11

Browse files
committed
Store Aladin API calls if widget is not ready
observe _is_loaded traitlet for a change to run all the stored API calls in the right order
1 parent 96277b2 commit a474d11

3 files changed

Lines changed: 57 additions & 54 deletions

File tree

examples/09_Displaying_Shapes.ipynb

Lines changed: 11 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,6 @@
5454
"outputs": [],
5555
"source": [
5656
"aladin = Aladin(target=\"M 51\", fov=1.2)\n",
57-
"aladin"
58-
]
59-
},
60-
{
61-
"cell_type": "code",
62-
"execution_count": null,
63-
"metadata": {},
64-
"outputs": [],
65-
"source": [
6657
"aladin.add_graphic_overlay_from_stcs(\n",
6758
" \"\"\"Polygon ICRS 202.63748 47.24951 202.46382 47.32391 202.46379 47.32391 202.45459\n",
6859
" 47.32391 202.34527 47.20597 202.34527 47.20596 202.34529 47.19710 202.51870\n",
@@ -77,7 +68,9 @@
7768
")\n",
7869
"aladin.add_graphic_overlay_from_stcs(\n",
7970
" \"Circle ICRS 202.4656816 +47.1999842 0.04\", color=\"#4488ee\"\n",
80-
")"
71+
")\n",
72+
"\n",
73+
"aladin"
8174
]
8275
},
8376
{
@@ -191,45 +184,16 @@
191184
"source": [
192185
"aladin.add_graphic_overlay_from_region([circle, ellipse, line, polygon, rectangle])"
193186
]
194-
}
195-
],
196-
"metadata": {
197-
"kernelspec": {
198-
"display_name": "base",
199-
"language": "python",
200-
"name": "python3"
201187
},
202-
"language_info": {
203-
"codemirror_mode": {
204-
"name": "ipython",
205-
"version": 3
206-
},
207-
"file_extension": ".py",
208-
"mimetype": "text/x-python",
209-
"name": "python",
210-
"nbconvert_exporter": "python",
211-
"pygments_lexer": "ipython3",
212-
"version": "3.11.8"
213-
},
214-
"toc": {
215-
"base_numbering": 1,
216-
"nav_menu": {},
217-
"number_sections": false,
218-
"sideBar": false,
219-
"skip_h1_title": false,
220-
"title_cell": "Table of Contents",
221-
"title_sidebar": "Contents",
222-
"toc_cell": false,
223-
"toc_position": {},
224-
"toc_section_display": false,
225-
"toc_window_display": false
226-
},
227-
"vscode": {
228-
"interpreter": {
229-
"hash": "85bb43f988bdbdc027a50b6d744a62eda8a76617af1f4f9b115d38242716dbac"
230-
}
188+
{
189+
"cell_type": "code",
190+
"execution_count": null,
191+
"metadata": {},
192+
"outputs": [],
193+
"source": []
231194
}
232-
},
195+
],
196+
"metadata": {},
233197
"nbformat": 4,
234198
"nbformat_minor": 4
235199
}

src/ipyaladin/utils/call_store.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from traitlets import (
2+
Any,
3+
)
4+
5+
6+
class CallStore:
7+
"""A simple call store.
8+
9+
This is used to store user calls to Aladin API in case the widget is not properly
10+
loaded on the JS side. Whenever it gets loaded, then the calls are run in the
11+
inserting order.
12+
"""
13+
14+
def __init__(self) -> None:
15+
self.calls = []
16+
17+
def add(self, func: Any, *args: Any, **kwargs: Any) -> None:
18+
"""Add a call to the store."""
19+
self.calls.append((func, args, kwargs))
20+
21+
def run_all(self) -> None:
22+
"""Run all the stored calls in the right order."""
23+
for func, args, kwargs in self.calls:
24+
func(*args, **kwargs)

src/ipyaladin/widget.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import io
1212
import pathlib
1313
from pathlib import Path
14-
import time
1514
from typing import ClassVar, Dict, Final, List, Optional, Tuple, Union
1615

1716
try: # drop this clause when we drop python 3.9
@@ -32,6 +31,7 @@
3231

3332
from .utils.exceptions import WidgetReducedError, WidgetNotReadyError
3433
from .utils._coordinate_parser import _parse_coordinate_string
34+
from .utils.call_store import CallStore
3535
from .elements.error_shape import (
3636
CircleError,
3737
EllipseError,
@@ -87,6 +87,8 @@
8787
def 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)
@@ -241,6 +245,17 @@ def __init__(self, *args: any, **init_options: any) -> None:
241245
self._init_options = init_options
242246
self.on_msg(self._handle_custom_message)
243247

248+
def on_load_change(change: traitlets.Dict) -> None:
249+
if change["new"]:
250+
self._call_store.run_all()
251+
252+
# There can be a race condition if the JS is already load
253+
# leading to _is_loaded = True before observing it.
254+
# If this is the case there should not be any problem because no python commands
255+
# are expected to be called on an object before the object itself
256+
# is instanciated.
257+
self.observe(on_load_change, names="_is_loaded")
258+
244259
def _handle_custom_message(self, _: any, message: dict, buffers: any) -> None:
245260
event_type = message["event_type"]
246261
if (

0 commit comments

Comments
 (0)