diff --git a/.travis.yml b/.travis.yml index ec01d131..d4c581c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,7 @@ script: - mypy --package webviz_config --ignore-missing-imports --disallow-untyped-defs --show-error-codes - webviz certificate + - webviz preferences --theme default - pytest tests --forked - pushd ./docs; python build_docs.py; popd diff --git a/webviz_config/__init__.py b/webviz_config/__init__.py index c6e1150e..764b64ef 100644 --- a/webviz_config/__init__.py +++ b/webviz_config/__init__.py @@ -2,12 +2,10 @@ from pkg_resources import get_distribution, DistributionNotFound +from ._theme_class import WebvizConfigTheme from ._localhost_token import LocalhostToken -from ._localhost_open_browser import LocalhostOpenBrowser -from ._localhost_certificate import LocalhostCertificate from ._is_reload_process import is_reload_process from ._plugin_abc import WebvizPluginABC, WebvizContainerABC -from ._theme_class import WebvizConfigTheme from ._shared_settings_subscriptions import SHARED_SETTINGS_SUBSCRIPTIONS warnings.simplefilter("ignore", DeprecationWarning) diff --git a/webviz_config/_user_data_dir.py b/webviz_config/_user_data_dir.py new file mode 100644 index 00000000..0b1ccb2d --- /dev/null +++ b/webviz_config/_user_data_dir.py @@ -0,0 +1,15 @@ +import sys +from pathlib import Path + + +def user_data_dir() -> Path: + """Returns platform specific path to store user application data + """ + + if sys.platform == "win32": + return Path.home() / "Application Data" / "webviz" + + if sys.platform == "darwin": + return Path.home() / "Library" / "Application Support" / "webviz" + + return Path.home() / ".local" / "share" / "webviz" diff --git a/webviz_config/_user_preferences.py b/webviz_config/_user_preferences.py new file mode 100644 index 00000000..c8d2df5a --- /dev/null +++ b/webviz_config/_user_preferences.py @@ -0,0 +1,52 @@ +import os +import json +import webbrowser +from typing import Optional + +from ._user_data_dir import user_data_dir +from .themes import installed_themes + +USER_SETTINGS_FILE = user_data_dir() / "user_settings.json" + + +def set_user_preferences( + theme: Optional[str] = None, browser: Optional[str] = None +) -> None: + + preferences = ( + json.loads(USER_SETTINGS_FILE.read_text()) + if USER_SETTINGS_FILE.is_file() + else {} + ) + + new_preferences = {} + + if theme is not None: + if theme not in installed_themes: + raise ValueError( + f"Theme {theme} is not one of the installed themes ({', '.join(installed_themes)})" + ) + new_preferences["theme"] = theme + + if browser is not None: + try: + webbrowser.get(using=browser) + except webbrowser.Error: + raise ValueError( + f"Could not find an installed browser with the name {browser}." + ) + + new_preferences["browser"] = browser + + if new_preferences: + preferences.update(new_preferences) + os.makedirs(USER_SETTINGS_FILE.parent, exist_ok=True) + USER_SETTINGS_FILE.write_text(json.dumps(preferences)) + + +def get_user_preference(setting: str) -> Optional[str]: + return ( + json.loads(USER_SETTINGS_FILE.read_text()).get(setting) + if USER_SETTINGS_FILE.is_file() + else None + ) diff --git a/webviz_config/certificate/__init__.py b/webviz_config/certificate/__init__.py new file mode 100644 index 00000000..dbdf4214 --- /dev/null +++ b/webviz_config/certificate/__init__.py @@ -0,0 +1 @@ +from ._localhost_certificate import LocalhostCertificate diff --git a/webviz_config/_certificate.py b/webviz_config/certificate/_certificate_generator.py similarity index 94% rename from webviz_config/_certificate.py rename to webviz_config/certificate/_certificate_generator.py index 29396c15..4300bac8 100644 --- a/webviz_config/_certificate.py +++ b/webviz_config/certificate/_certificate_generator.py @@ -1,6 +1,5 @@ import os import re -import sys import pathlib import getpass import datetime @@ -13,7 +12,8 @@ from cryptography.hazmat.primitives import serialization, hashes from cryptography.hazmat.primitives.asymmetric import rsa -from .utils import terminal_colors +from .._user_data_dir import user_data_dir +from ..utils import terminal_colors NAME = x509.Name( @@ -33,19 +33,6 @@ SERVER_CRT_FILENAME = "server.crt" -def user_data_dir() -> str: - """Returns platform specific path to store user application data - """ - - if sys.platform == "win32": - return os.path.normpath(os.path.expanduser("~/Application Data/webviz")) - - if sys.platform == "darwin": - return os.path.expanduser("~/Library/Application Support/webviz") - - return os.path.expanduser("~/.local/share/webviz") - - def create_key(key_path: str) -> rsa.RSAPrivateKey: key = rsa.generate_private_key( diff --git a/webviz_config/_localhost_certificate.py b/webviz_config/certificate/_localhost_certificate.py similarity index 86% rename from webviz_config/_localhost_certificate.py rename to webviz_config/certificate/_localhost_certificate.py index 34cfca06..dcd171a9 100644 --- a/webviz_config/_localhost_certificate.py +++ b/webviz_config/certificate/_localhost_certificate.py @@ -3,8 +3,12 @@ import shutil import tempfile -from ._is_reload_process import is_reload_process -from ._certificate import create_certificate, SERVER_KEY_FILENAME, SERVER_CRT_FILENAME +from .._is_reload_process import is_reload_process +from ._certificate_generator import ( + create_certificate, + SERVER_KEY_FILENAME, + SERVER_CRT_FILENAME, +) class LocalhostCertificate: diff --git a/webviz_config/command_line.py b/webviz_config/command_line.py index 67fb67b0..8a5020c2 100644 --- a/webviz_config/command_line.py +++ b/webviz_config/command_line.py @@ -1,7 +1,8 @@ import argparse from ._build_webviz import build_webviz -from ._certificate import create_ca +from .certificate._certificate_generator import create_ca +from ._user_preferences import set_user_preferences, get_user_preference def main() -> None: @@ -19,7 +20,7 @@ def main() -> None: # Add "build" argument parser: - parser_build = subparsers.add_parser("build", help="Build a Webviz " "Dash App") + parser_build = subparsers.add_parser("build", help="Build a Webviz Dash App") parser_build.add_argument( "yaml_file", type=str, help="Path to YAML configuration file" @@ -33,7 +34,12 @@ def main() -> None: "and saved to the given folder.", ) parser_build.add_argument( - "--theme", type=str, default="default", help="Which installed theme to use." + "--theme", + type=str, + default=get_user_preference("theme") + if get_user_preference("theme") is not None + else "default", + help="Which installed theme to use.", ) parser_build.add_argument( "--loglevel", @@ -70,6 +76,38 @@ def main() -> None: parser_cert.set_defaults(func=create_ca) + # Add "preferences" parser: + + parser_preferences = subparsers.add_parser( + "preferences", help="Set preferred webviz settings", + ) + + parser_preferences.add_argument( + "--browser", + type=str, + help="Set the name of your preferred browser, " + "in which localhost applications will open automatically.", + ) + parser_preferences.add_argument( + "--theme", + type=str, + help="Set your preferred Webviz theme, which will be used if " + "'--theme' is not provided as an argument with the 'webviz build' command.", + ) + + def entrypoint_preferences(args: argparse.Namespace) -> None: + + if args.theme is not None: + set_user_preferences(theme=args.theme) + + if args.browser is not None: + set_user_preferences(browser=args.browser) + + print(f"Preferred theme: {get_user_preference('theme')}") + print(f"Preferred browser: {get_user_preference('browser')}") + + parser_preferences.set_defaults(func=entrypoint_preferences) + # Do the argument parsing: args = parser.parse_args() diff --git a/webviz_config/templates/webviz_template.py.jinja2 b/webviz_config/templates/webviz_template.py.jinja2 index 7fe7e89f..f3b1c3a2 100644 --- a/webviz_config/templates/webviz_template.py.jinja2 +++ b/webviz_config/templates/webviz_template.py.jinja2 @@ -16,6 +16,7 @@ import dash_core_components as dcc import dash_html_components as html from flask_talisman import Talisman import webviz_config +import webviz_config.certificate from webviz_config.themes import installed_themes from webviz_config.common_cache import CACHE from webviz_config.webviz_store import WEBVIZ_STORAGE @@ -115,14 +116,14 @@ if __name__ == "__main__": port = webviz_config.utils.get_available_port() token = webviz_config.LocalhostToken(app.server, port).one_time_token - webviz_config.LocalhostOpenBrowser(port, token) + webviz_config.utils.LocalhostOpenBrowser(port, token) webviz_config.utils.silence_flask_startup() app.run_server( host="localhost", port=port, - ssl_context=webviz_config.LocalhostCertificate().ssl_context, + ssl_context=webviz_config.certificate.LocalhostCertificate().ssl_context, debug=False, use_reloader={{not portable}}, {% if not portable -%} diff --git a/webviz_config/utils/__init__.py b/webviz_config/utils/__init__.py index 043a00a5..349eac03 100644 --- a/webviz_config/utils/__init__.py +++ b/webviz_config/utils/__init__.py @@ -1,3 +1,4 @@ +from ._localhost_open_browser import LocalhostOpenBrowser from ._available_port import get_available_port from ._silence_flask_startup import silence_flask_startup from ._dash_component_utils import calculate_slider_step diff --git a/webviz_config/_localhost_open_browser.py b/webviz_config/utils/_localhost_open_browser.py similarity index 87% rename from webviz_config/_localhost_open_browser.py rename to webviz_config/utils/_localhost_open_browser.py index c684d36b..da7e7559 100644 --- a/webviz_config/_localhost_open_browser.py +++ b/webviz_config/utils/_localhost_open_browser.py @@ -1,11 +1,13 @@ import os import time import urllib +import warnings import threading import webbrowser -from ._is_reload_process import is_reload_process -from .utils import terminal_colors +from .._is_reload_process import is_reload_process +from .._user_preferences import get_user_preference +from . import terminal_colors class LocalhostOpenBrowser: @@ -47,6 +49,13 @@ def _url(self, with_token: bool = False, https: bool = True) -> str: @staticmethod def _get_browser_controller() -> webbrowser.BaseBrowser: + + if get_user_preference("browser") is not None: + try: + return webbrowser.get(using=get_user_preference("browser")) + except webbrowser.Error: + warnings.warn("Could not find the user preferred browser.") + for browser in ["chrome", "chromium-browser"]: try: return webbrowser.get(using=browser)