Skip to content

Commit ec5e1c4

Browse files
committed
Merge remote-tracking branch 'upstream/dev' into dev_cobalt
2 parents 175bb40 + 2f2ccf5 commit ec5e1c4

6 files changed

Lines changed: 67 additions & 55 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020

2121
- `rotation` parameter can now be called to rotate the ipyaladin viewer [#146]
2222

23+
## Fixed
24+
25+
- observe `_is_loaded` trait to call the Aladin API when the javascript is loaded [#149]
26+
2327
## [0.6.0]
2428

2529
## Added

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
}

js/widget.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function initAladinLite(model, el) {
3939
});
4040
const wcs = { ...aladin.getViewWCS() };
4141
model.set("_wcs", wcs);
42-
const rotation = { ...aladin.getRotation() };
42+
const rotation = aladin.getRotation();
4343
model.set("_rotation", rotation);
4444

4545
model.set("_is_loaded", true);

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)
@@ -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 (

src/tests/test_aladin.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515

1616
aladin = Aladin()
1717

18+
# Tell python that the JS part is loaded
19+
# so that adding overlay requests are sent
20+
# through the events/traitlets system.
21+
aladin._is_loaded = True
22+
1823

1924
# monkeypatched sesame call to avoid remote access during tests
2025
@pytest.fixture

0 commit comments

Comments
 (0)