From 00708ae509a3e809c4e5d5ecc9169514ab03c918 Mon Sep 17 00:00:00 2001 From: Tim Pillinger <26465611+wxtim@users.noreply.github.com> Date: Thu, 9 May 2024 10:10:53 +0100 Subject: [PATCH] Avoid requiring jupyter_config.py in to be in CYLC_SITE_CONF_PATH/uiserver Remove the need for the uiserver subdirectory. Ensure that user is warned if file at CYLC_SITE_CONF_PATH is * Unreadable * Non-existent --- changes.d/594.fix | 1 + cylc/uiserver/config_util.py | 3 +- cylc/uiserver/jupyterhub_config.py | 26 ++++++++++--- cylc/uiserver/tests/test_config.py | 4 +- cylc/uiserver/tests/test_jupyterhub_config.py | 39 +++++++++++++++++++ 5 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 changes.d/594.fix create mode 100644 cylc/uiserver/tests/test_jupyterhub_config.py diff --git a/changes.d/594.fix b/changes.d/594.fix new file mode 100644 index 00000000..a66f864c --- /dev/null +++ b/changes.d/594.fix @@ -0,0 +1 @@ +Avoid requiring jupyter_config.py in to be in uiserver subdirectory when using CYLC_SITE_CONF_PATH \ No newline at end of file diff --git a/cylc/uiserver/config_util.py b/cylc/uiserver/config_util.py index 3b34af5a..e3424bec 100644 --- a/cylc/uiserver/config_util.py +++ b/cylc/uiserver/config_util.py @@ -30,7 +30,8 @@ ) # base configuration - always used -DEFAULT_CONF_PATH: Path = Path(uis_pkg).parent / 'jupyter_config.py' +CONF_FILE_NAME = 'jupyter_config.py' +DEFAULT_CONF_PATH: Path = Path(uis_pkg).parent / CONF_FILE_NAME UISERVER_DIR = 'uiserver' # UIS configuration dirs SITE_CONF_ROOT: Path = Path( diff --git a/cylc/uiserver/jupyterhub_config.py b/cylc/uiserver/jupyterhub_config.py index e1e5e87d..40e27107 100644 --- a/cylc/uiserver/jupyterhub_config.py +++ b/cylc/uiserver/jupyterhub_config.py @@ -25,8 +25,8 @@ from pathlib import Path from cylc.uiserver.config_util import ( + CONF_FILE_NAME, DEFAULT_CONF_PATH, - UISERVER_DIR, USER_CONF_ROOT, SITE_CONF_ROOT, get_conf_dir_hierarchy @@ -46,10 +46,8 @@ def _load(path): def load() -> None: """Load the relevant UIS/Hub configuration files.""" if os.getenv('CYLC_SITE_CONF_PATH'): - site_conf_path: Path = Path( - os.environ['CYLC_SITE_CONF_PATH'], - UISERVER_DIR - ) + site_conf_path: Path = check_cylc_site_conf_path( + Path(os.environ['CYLC_SITE_CONF_PATH'])) else: site_conf_path = SITE_CONF_ROOT base_config_paths = [site_conf_path, USER_CONF_ROOT] @@ -61,6 +59,24 @@ def load() -> None: _load(Path(path)) +def check_cylc_site_conf_path(path_: Path) -> Path: + """Check path set by CYLC_SITE_CONF_PATH + + 1. Path exists and has a file. + 2. File is readable. + """ + conf_file_path = (path_ / CONF_FILE_NAME) + if not conf_file_path.is_file(): + LOG.warning( + f'Config path {path_} set by' + ' "CYLC_SITE_CONF_PATH" does not exist or.' + ' does not have a jupyter_config.py file.') + elif not os.access(conf_file_path, os.R_OK): + LOG.error(f'Unable to read config file at {path_}') + return SITE_CONF_ROOT + return path_ + + hub_version = os.environ.get('CYLC_HUB_VERSION') if hub_version: # auto-load the config (jupyterhub requirement) diff --git a/cylc/uiserver/tests/test_config.py b/cylc/uiserver/tests/test_config.py index e7349c84..c09abef2 100644 --- a/cylc/uiserver/tests/test_config.py +++ b/cylc/uiserver/tests/test_config.py @@ -77,8 +77,8 @@ def test_cylc_site_conf_path(clear_env, capload, monkeypatch): load() assert capload == [ SYS_CONF, - Path('elephant/uiserver/jupyter_config.py'), - Path('elephant/uiserver/0/jupyter_config.py'), + Path('elephant/jupyter_config.py'), + Path('elephant/0/jupyter_config.py'), Path(USER_CONF / 'jupyter_config.py'), Path(USER_CONF / '0/jupyter_config.py') ] diff --git a/cylc/uiserver/tests/test_jupyterhub_config.py b/cylc/uiserver/tests/test_jupyterhub_config.py new file mode 100644 index 00000000..6b0862ee --- /dev/null +++ b/cylc/uiserver/tests/test_jupyterhub_config.py @@ -0,0 +1,39 @@ +# Copyright (C) NIWA & British Crown (Met Office) & Contributors. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""Tests for jupyterhub_config module.""" + +from cylc.uiserver.jupyterhub_config import check_cylc_site_conf_path +from cylc.uiserver.config_util import CONF_FILE_NAME, SITE_CONF_ROOT + + +def test_cylc_site_conf_path_ok(tmp_path, caplog): + """Method passes valid file without comment""" + (tmp_path / CONF_FILE_NAME).touch() + assert check_cylc_site_conf_path(tmp_path) == tmp_path + assert caplog.messages == [] + + +def test_cylc_site_conf_path_unreadable(tmp_path, caplog): + """Method logs error because file exists but is unreadable.""" + (tmp_path / CONF_FILE_NAME).touch() + (tmp_path / CONF_FILE_NAME).chmod(0) + assert check_cylc_site_conf_path(tmp_path) == SITE_CONF_ROOT + assert caplog.messages[0].startswith('Unable to read config file at') + + +def test_cylc_site_conf_path_empty(tmp_path, caplog): + """Method logs error because file does not exist.""" + assert check_cylc_site_conf_path(tmp_path) == tmp_path + assert 'does not exist' in caplog.messages[0]