Skip to content

Commit

Permalink
replace voila with solara for CLI/standalone app (spacetelescope#2909)
Browse files Browse the repository at this point in the history
* replace voila with solara in cli
* feat: use solara for pyinstaller
* remove no longer needed share/jupyter/nbconvert directories
* set page title to "Jdaviz"
* override favicon (currently with cubeviz icon)
* cli: remove unneeded logic for python<3.10
* update/remove other mentions of voila across codebase
* add solara to devdeps
* bump ipypopout

---------

Co-authored-by: Mario Buikhuizen <[email protected]>
Co-authored-by: Maarten Breddels <[email protected]>
  • Loading branch information
3 people authored and cshanahan1 committed Sep 6, 2024
1 parent a4e40c8 commit cd20859
Show file tree
Hide file tree
Showing 33 changed files with 207 additions and 907 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ body:
import ipygoldenlayout; print("ipygoldenlayout", ipygoldenlayout.__version__)
import ipypopout; print("ipypopout", ipypopout.__version__)
import jinja2; print("Jinja2", jinja2.__version__)
import voila; print("voila", voila.__version__)
import solara; print("solara", solara.__version__)
import vispy; print("vispy", vispy.__version__)
import sidecar; print("sidecar", sidecar.__version__)
import jdaviz; print("Jdaviz", jdaviz.__version__)
8 changes: 4 additions & 4 deletions .github/workflows/standalone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ jobs:
- name: Install pytest
run: pip install pytest-playwright

- name: Wait for Voila to get online
- name: Wait for Solara to get online
uses: ifaxity/wait-on-action@a7d13170ec542bdca4ef8ac4b15e9c6aa00a6866 # v1.2.1
with:
resource: tcp:8866
resource: tcp:8765
timeout: 60000

- name: Test standalone
Expand Down Expand Up @@ -174,10 +174,10 @@ jobs:
- name: Install pytest
run: pip install pytest-playwright

- name: Wait for Voila to get online
- name: Wait for Solara to get online
uses: ifaxity/wait-on-action@a7d13170ec542bdca4ef8ac4b15e9c6aa00a6866 # v1.2.1
with:
resource: tcp:8866
resource: tcp:8765
timeout: 60000

- name: Test standalone
Expand Down
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ New Features

- Plugins can now expose in-UI API hints. [#3137, #3159]

- The standalone version of jdaviz now uses solara instead of voila, resulting in faster load times. [#2909]

Cubeviz
^^^^^^^

Expand Down
30 changes: 30 additions & 0 deletions assets/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def pytest_configure(config):
PYTEST_HEADER_MODULES['ipysplitpanes'] = 'ipysplitpanes'
PYTEST_HEADER_MODULES['ipygoldenlayout'] = 'ipygoldenlayout'
PYTEST_HEADER_MODULES['ipypopout'] = 'ipypopout'
PYTEST_HEADER_MODULES['voila'] = 'voila'
PYTEST_HEADER_MODULES['solara'] = 'solara'
PYTEST_HEADER_MODULES['vispy'] = 'vispy'
PYTEST_HEADER_MODULES['gwcs'] = 'gwcs'
PYTEST_HEADER_MODULES['asdf'] = 'asdf'
Expand Down
2 changes: 1 addition & 1 deletion docs/dev/infrastructure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ The target interfaces are:
* **Desktop**: This interface is meant to behave like a more traditional "desktop app",
i.e., a window with a fixed set of functionality and a particular layout for a
specific set of scientific use cases. This interface is accessed via a
`Voilà <https://voila.readthedocs.io>`_ wrapper that loads the same machinery as the
`Solara <https://solara.dev>`_ wrapper that loads the same machinery as the
other interfaces but presents the outputs of notebook "cells" as the only view.
This trades the flexibility of the notebook interface for a consistent and
reproducible layout and simpler interface without the distraction of the notebook
Expand Down
10 changes: 5 additions & 5 deletions docs/dev/win_dev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ under the source checkout's root directory, you will need to rebuild
the package even in editable install mode. Otherwise, this should not
affect your development experience.

WSL2 and voila
--------------
WSL2 and Solara
---------------

``voila`` is unable to display when WSL2 cannot start up the
``solara`` is unable to display when WSL2 cannot start up the
Windows-side browser executable. Unfortunately, unlike Jupyter
notebook, ``voila`` does not have a ``--no-browser`` option
notebook, ``solara`` does not have a ``--no-browser`` option
with a tokenized URL you can copy-and-paste manually on the
Windows side (see https://github.com/voila-dashboards/voila/issues/773).
Windows side.
Therefore, you might need to install Jdaviz natively on Windows
to test its standalone application functionality.
1 change: 0 additions & 1 deletion docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ instead of ``pip``:
conda install bottleneck
conda install -c conda-forge notebook
conda install -c conda-forge jupyterlab
conda install -c conda-forge voila
You might also want to enable the ``ipywidgets`` notebook extension, as follows:

Expand Down
112 changes: 26 additions & 86 deletions jdaviz/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@
import inspect
import os
import pathlib
import sys
import tempfile

from voila.app import Voila
from voila.configuration import VoilaConfiguration

from jdaviz import __version__
from jdaviz.app import _verbosity_levels, ALL_JDAVIZ_CONFIGS
Expand All @@ -22,7 +17,7 @@


def main(filepaths=None, layout='default', instrument=None, browser='default',
theme='light', verbosity=DEFAULT_VERBOSITY, history_verbosity=DEFAULT_HISTORY_VERBOSITY,
theme='auto', verbosity=DEFAULT_VERBOSITY, history_verbosity=DEFAULT_HISTORY_VERBOSITY,
hotreload=False):
"""
Start a Jdaviz application instance with data loaded from FILENAME.
Expand All @@ -37,22 +32,15 @@ def main(filepaths=None, layout='default', instrument=None, browser='default',
Specifies which instrument parser to use for Mosviz, if applicable.
browser : str, optional
Path to browser executable.
theme : {'light', 'dark'}
Theme to use for Voila app or Jupyter Lab.
theme : {'auto', 'light', 'dark'}
Theme to use for application.
verbosity : {'debug', 'info', 'warning', 'error'}
Verbosity of the popup messages in the application.
history_verbosity : {'debug', 'info', 'warning', 'error'}
Verbosity of the history logger in the application.
hotreload : bool
Whether to enable hot-reloading of the UI (for development)
"""
import logging # Local import to avoid possibly messing with JWST pipeline logger.

# Tornado Webserver py3.8 compatibility hotfix for windows
if sys.platform == 'win32':
import asyncio
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

if filepaths:
# Convert paths to posix string; windows paths are not JSON compliant
file_list = [pathlib.Path(f).absolute().as_posix() for f in filepaths]
Expand All @@ -63,74 +51,30 @@ def main(filepaths=None, layout='default', instrument=None, browser='default',
else:
file_list = []

if layout == '':
if len(file_list) <= 1:
notebook = "jdaviz_cli_launcher.ipynb"
else:
raise ValueError("'layout' argument is required when specifying multiple files")
else:
notebook = "jdaviz_cli.ipynb"

with open(JDAVIZ_DIR / notebook) as f:
notebook_template = f.read()

start_dir = os.path.abspath('.')
if layout == '' and len(file_list) > 1:
raise ValueError("'layout' argument is required when specifying multiple files")

# Keep track of start directory in environment variable so that it can be
# easily accessed e.g. in the file load dialog.
os.environ['JDAVIZ_START_DIR'] = start_dir

nbdir = tempfile.mkdtemp()

os.environ['JDAVIZ_START_DIR'] = os.path.abspath('.')

from solara.__main__ import cli
from jdaviz import solara
solara.config = layout.capitalize()
solara.data_list = file_list
if layout == 'mosviz':
solara.load_data_kwargs = {'instrument': instrument}
solara.theme = theme
solara.jdaviz_verbosity = verbosity
solara.jdaviz_history_verbosity = history_verbosity
args = []
if hotreload:
notebook_template = notebook_template.replace("# PREFIX", "from jdaviz import enable_hot_reloading; enable_hot_reloading()") # noqa: E501

with open(os.path.join(nbdir, 'notebook.ipynb'), 'w') as nbf:
nbf.write(
notebook_template
.replace('CONFIG', layout.capitalize())
.replace('DATA_LIST', str(file_list))
.replace('JDAVIZ_VERBOSITY', verbosity)
.replace('JDAVIZ_HISTORY_VERBOSITY', history_verbosity)
# Mosviz specific changes
.replace('load_data(data', 'load_data(directory=data' if layout == 'mosviz' else 'load_data(data') # noqa: E501
.replace(') #ADDITIONAL_LOAD_DATA_ARGS', f', instrument=\'{instrument}\')' if layout == 'mosviz' else ')') # noqa: E501
.strip()
)

os.chdir(nbdir)

try:
logging.getLogger('tornado.access').disabled = True
Voila.notebook_path = 'notebook.ipynb'
VoilaConfiguration.template = 'jdaviz-default'
VoilaConfiguration.enable_nbextensions = True
VoilaConfiguration.file_whitelist = ['.*']
VoilaConfiguration.theme = theme
if browser != 'default':
Voila.browser = browser

voila = Voila.instance()
# monkey patch listen, so we can get a handle on the kernel_manager
# after it is created
previous_listen = voila.listen

def listen(*args, **kwargs):
# monkey patch remove_kernel, so we can stop the event loop
# when a kernel is removed (which means the browser page was closed)
previous_remove_kernel = voila.kernel_manager.remove_kernel

def remove_kernel(kernel_id):
previous_remove_kernel(kernel_id)
voila.ioloop.stop()

voila.kernel_manager.remove_kernel = remove_kernel
return previous_listen(*args, **kwargs)

voila.listen = listen
sys.exit(voila.launch_instance(argv=[]))
finally:
os.chdir(start_dir)
args += ['--auto-restart']
else:
args += ['--production']
cli(['run', 'jdaviz.solara',
'--theme-loader', 'plain',
'--theme-variant', theme] + args)


def _main(config=None):
Expand All @@ -157,13 +101,9 @@ def _main(config=None):
help='Verbosity of the application for popup snackbars.')
parser.add_argument('--history-verbosity', choices=_verbosity_levels, default='info',
help='Verbosity of the logger history.')
if sys.version_info >= (3, 9):
# Also enables --no-hotreload
parser.add_argument('--hotreload', action=argparse.BooleanOptionalAction, default=False,
help='Whether to enable hot-reloading of the UI (for development).')
else:
parser.add_argument('--hotreload', action='store_true', default=False,
help='Enable hot-reloading of the UI (for development).')
# Also enables --no-hotreload
parser.add_argument('--hotreload', action=argparse.BooleanOptionalAction, default=False,
help='Whether to enable hot-reloading of the UI (for development).')
parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
args = parser.parse_args()

Expand Down
2 changes: 1 addition & 1 deletion jdaviz/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ def pytest_configure(config):
PYTEST_HEADER_MODULES['ipysplitpanes'] = 'ipysplitpanes'
PYTEST_HEADER_MODULES['ipygoldenlayout'] = 'ipygoldenlayout'
PYTEST_HEADER_MODULES['ipypopout'] = 'ipypopout'
PYTEST_HEADER_MODULES['voila'] = 'voila'
PYTEST_HEADER_MODULES['solara'] = 'solara'
PYTEST_HEADER_MODULES['vispy'] = 'vispy'
PYTEST_HEADER_MODULES['gwcs'] = 'gwcs'
PYTEST_HEADER_MODULES['asdf'] = 'asdf'
Expand Down
26 changes: 16 additions & 10 deletions jdaviz/core/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,16 @@ class Launcher(v.VuetifyTemplate):
mosviz_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'mosviz_icon.svg'), 'svg+xml')).tag(sync=True) # noqa
imviz_icon = Unicode(read_icon(os.path.join(ICON_DIR, 'imviz_icon.svg'), 'svg+xml')).tag(sync=True) # noqa

def __init__(self, main, configs=ALL_JDAVIZ_CONFIGS, filepath='', height=None, *args, **kwargs):
def __init__(self, main=None, configs=ALL_JDAVIZ_CONFIGS, filepath='',
height=None, *args, **kwargs):
self.vdocs = 'latest' if 'dev' in __version__ else 'v'+__version__

if main is None:
main = v.Sheet(class_="mx-25",
attributes={"id": "popout-widget-container"},
color="#00212C",
height=height)

self.main = main
self.configs = configs
self.height = f"{height}px" if isinstance(height, int) else height
Expand Down Expand Up @@ -193,14 +200,19 @@ def vue_launch_config(self, event):
config = event.get('config')
helper = _launch_config_with_data(config, self.loaded_data,
filepath=self.filepath, show=False)
if self.height != '100%':
if self.height not in ['100%', '100vh']:
# We're in jupyter mode. Set to default height
default_height = helper.app.state.settings['context']['notebook']['max_height']
helper.app.layout.height = default_height
self.main.height = default_height
self.main.color = 'transparent'
self.main.children = [helper.app]

@property
def main_with_launcher(self):
self.main.children = [self]
return self.main


def show_launcher(configs=ALL_JDAVIZ_CONFIGS, filepath='', height='450px'):
'''Display an interactive Jdaviz launcher to select your data and compatible configuration
Expand All @@ -217,11 +229,5 @@ def show_launcher(configs=ALL_JDAVIZ_CONFIGS, filepath='', height='450px'):
'''
# Color defined manually due to the custom theme not being defined yet (in main_styles.vue)
height = f"{height}px" if isinstance(height, int) else height
main = v.Sheet(class_="mx-25",
attributes={"id": "popout-widget-container"},
color="#00212C",
height=height,
_metadata={'mount_id': 'content'})
main.children = [Launcher(main, configs, filepath, height)]

show_widget(main, loc='inline', title=None)
launcher = Launcher(None, configs, filepath, height)
show_widget(launcher.main_with_launcher, loc='inline', title=None)
40 changes: 0 additions & 40 deletions jdaviz/jdaviz_cli.ipynb

This file was deleted.

Loading

0 comments on commit cd20859

Please sign in to comment.