diff --git a/jdaviz/app.py b/jdaviz/app.py index 6c448dd76f..6877e3c29d 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -151,7 +151,7 @@ def to_unit(self, data, cid, values, original_units, target_units): ipyvue.register_component_from_file('g-viewer-tab', "container.vue", __file__) -style_registry.instance.add((__file__, 'main_styles.vue')) +style_registry.add((__file__, 'main_styles.vue')) class ApplicationState(State): @@ -262,7 +262,7 @@ def __init__(self, configuration=None, *args, **kwargs): self._verbosity = 'warning' self._history_verbosity = 'info' self.popout_button = PopoutButton(self) - self.style_registry_instance = style_registry.instance + self.style_registry_instance = style_registry.get_style_registry() # Generate a state object for this application to maintain the state of # the user interface. diff --git a/jdaviz/style_registry.py b/jdaviz/style_registry.py index ce03f2aa48..ce63ffe112 100644 --- a/jdaviz/style_registry.py +++ b/jdaviz/style_registry.py @@ -1,3 +1,7 @@ +from pathlib import Path +import typing as t +import threading + from traitlets import Any, Dict, Instance, default from ipywidgets import widget_serialization from ipyvuetify import VuetifyTemplate @@ -5,6 +9,39 @@ from jdaviz.core.style_widget import StyleWidget +T = t.TypeVar("T") +_style_paths: t.Dict[int, Path] = {} + + +# this is a temporary decorator until we can depend on solara, and solara has +# an equivalent implementation. +def _singleton(factory: [t.Callable[[], T]]) -> t.Callable[[], T]: + def _get_unique_key(): + try: + import solara.server.kernel_context as kernel_context + + try: + kc = kernel_context.get_current_context() + return kc.id + except RuntimeError: + pass # not running in a solara virtual kernel + except ModuleNotFoundError: + return "not-in-solara" + + instances: t.Dict[str, T] = {} + lock = threading.Lock() + + def wrapper(*args, **kwargs) -> T: + key = _get_unique_key() + if key not in instances: + with lock: + if key not in instances: + instances[key] = factory(*args, **kwargs) + return instances[key] + + return wrapper + + class StyleRegistry(VuetifyTemplate): style_widgets = Dict(default_value={}).tag(sync=True, **widget_serialization) @@ -19,20 +56,17 @@ def _template(self): """ - def add(self, path): - if hash(path) in self.style_widgets: - return - self.style_widgets = {**self.style_widgets, hash(path): StyleWidget(path)} - -instance = StyleRegistry() +def add(path): + if hash(path) in _style_paths: + return + _style_paths[hash(path)] = path class PopoutStyleWrapper(VuetifyTemplate): content = Any().tag(sync=True, **widget_serialization) style_registry_instance = Instance( StyleRegistry, - default_value=instance ).tag(sync=True, **widget_serialization) @default("template") @@ -45,3 +79,15 @@ def _template(self): """ + + @default("style_registry_instance") + @_singleton + def _default_style_registry_instance(self): + return get_style_registry() + + +@_singleton +def get_style_registry(): + return StyleRegistry( + style_widgets={key: StyleWidget(path) for key, path in _style_paths.items()} + )