diff --git a/.github/workflows/test_pyqt5.yml b/.github/workflows/test_pyqt5.yml index 86216478..b2d10cae 100644 --- a/.github/workflows/test_pyqt5.yml +++ b/.github/workflows/test_pyqt5.yml @@ -52,18 +52,21 @@ jobs: python -m pip install --upgrade pip python -m pip install ruff pytest httpx pip install PyQt5 - if [ "${{ github.ref_name }}" = "develop" ]; then + if [ "${{ github.base_ref || github.ref_name }}" = "develop" ]; then # Clone and install development versions of key dependencies with editable install cd .. git clone --depth 1 https://github.com/PlotPyStack/PythonQwt.git git clone --depth 1 --branch develop https://github.com/PlotPyStack/guidata.git git clone --depth 1 --branch develop https://github.com/PlotPyStack/plotpy.git - git clone --depth 1 --branch develop https://github.com/DataLab-Platform/sigima.git + DEPENDENCY_BRANCH="${{ github.head_ref || github.ref_name }}" + git clone --depth 1 --branch "$DEPENDENCY_BRANCH" https://github.com/DataLab-Platform/sigima.git || git clone --depth 1 --branch develop https://github.com/DataLab-Platform/sigima.git + git clone --depth 1 --branch "$DEPENDENCY_BRANCH" https://github.com/DataLab-Platform/SigimaX.git || git clone --depth 1 --branch develop https://github.com/DataLab-Platform/SigimaX.git cd DataLab pip install -e ../guidata pip install -e ../PythonQwt pip install -e ../plotpy pip install -e ../sigima + pip install -e ../SigimaX --no-deps # Install tomli for TOML parsing (safe if already present) pip install tomli # Extract dependencies and save to file, then install @@ -71,7 +74,7 @@ jobs: pip install -r deps.txt # Install DataLab without dependencies pip install --no-deps . - elif [ "${{ github.ref_name }}" = "release" ]; then + elif [ "${{ github.base_ref || github.ref_name }}" = "release" ]; then # Clone dependencies from release branches (with fallback to main/master) cd .. # Try cloning PythonQwt from main or master diff --git a/.github/workflows/test_pyqt6.yml b/.github/workflows/test_pyqt6.yml index 2bd710dd..afbe83ef 100644 --- a/.github/workflows/test_pyqt6.yml +++ b/.github/workflows/test_pyqt6.yml @@ -52,18 +52,21 @@ jobs: python -m pip install --upgrade pip python -m pip install ruff pytest httpx pip install PyQt6 - if [ "${{ github.ref_name }}" = "develop" ]; then + if [ "${{ github.base_ref || github.ref_name }}" = "develop" ]; then # Clone and install development versions of key dependencies with editable install cd .. git clone --depth 1 https://github.com/PlotPyStack/PythonQwt.git git clone --depth 1 --branch develop https://github.com/PlotPyStack/guidata.git git clone --depth 1 --branch develop https://github.com/PlotPyStack/plotpy.git - git clone --depth 1 --branch develop https://github.com/DataLab-Platform/sigima.git + DEPENDENCY_BRANCH="${{ github.head_ref || github.ref_name }}" + git clone --depth 1 --branch "$DEPENDENCY_BRANCH" https://github.com/DataLab-Platform/sigima.git || git clone --depth 1 --branch develop https://github.com/DataLab-Platform/sigima.git + git clone --depth 1 --branch "$DEPENDENCY_BRANCH" https://github.com/DataLab-Platform/SigimaX.git || git clone --depth 1 --branch develop https://github.com/DataLab-Platform/SigimaX.git cd DataLab pip install -e ../guidata pip install -e ../PythonQwt pip install -e ../plotpy pip install -e ../sigima + pip install -e ../SigimaX --no-deps # Install tomli for TOML parsing (safe if already present) pip install tomli # Extract dependencies and save to file, then install @@ -71,7 +74,7 @@ jobs: pip install -r deps.txt # Install DataLab without dependencies pip install --no-deps . - elif [ "${{ github.ref_name }}" = "release" ]; then + elif [ "${{ github.base_ref || github.ref_name }}" = "release" ]; then # Clone dependencies from release branches (with fallback to main/master) cd .. # Try cloning PythonQwt from main or master diff --git a/datalab/config/persistence.py b/datalab/config/persistence.py index 6bcf5740..83d58f9f 100644 --- a/datalab/config/persistence.py +++ b/datalab/config/persistence.py @@ -479,6 +479,12 @@ def remove_section(self, section) -> bool: if not isinstance(_confmod.CONF, DataLabUserConfig): _confmod.CONF = DataLabUserConfig({}) +# The configuration directory name only depends on the application version, so it is +# bound as soon as the backend is installed: ``get_config_path`` may be called at +# import time - e.g. by ``datalab.plugins`` - long before ``initialize()`` has run, and +# an unnamed guidata ``UserConfig`` would silently resolve to ``~/.none``. +_confmod.CONF.set_application(get_config_app_name(), CONF_VERSION, load=False) + def atomic_save_configuration(config: AppUserConfig) -> None: """Atomically write a configuration backend to its target filename.""" diff --git a/datalab/tests/backbone/config_persistence_unit_test.py b/datalab/tests/backbone/config_persistence_unit_test.py index 0bdfa862..12077855 100644 --- a/datalab/tests/backbone/config_persistence_unit_test.py +++ b/datalab/tests/backbone/config_persistence_unit_test.py @@ -5,7 +5,10 @@ (:mod:`datalab.config.persistence`). """ +import os import shutil +import subprocess +import sys from pathlib import Path import guidata.dataset as gds @@ -270,3 +273,36 @@ def test_invalid_dataset_option_is_removed_on_load() -> None: assert not conf.has_option(section, ini_key) assert options.sig_shape_param.get() is None assert not options.is_option_initialized("sig_shape_param") + + +def test_config_app_name_is_bound_at_import_time(tmp_path) -> None: + """Import-time consumers resolve paths under the application directory. + + ``datalab.plugins`` resolves its default path when imported, which happens long + before ``initialize()`` runs: an unnamed backend would silently relocate user + plugins to ``~/.none/plugins``. A fresh interpreter is required, as the import + order cannot be reproduced in the current one. + """ + env = dict( + os.environ, + HOME=str(tmp_path), + USERPROFILE=str(tmp_path), + XDG_CONFIG_HOME=str(tmp_path), + QT_QPA_PLATFORM="offscreen", + ) + code = ( + "import datalab.plugins;" + "from datalab.config.appinfo import get_config_app_name;" + "print(datalab.plugins.PLUGINS_DEFAULT_PATH);" + "print(get_config_app_name())" + ) + stdout = subprocess.run( + [sys.executable, "-c", code], + env=env, + capture_output=True, + text=True, + check=True, + ).stdout + plugin_path, app_name = stdout.splitlines()[-2:] + + assert Path(plugin_path).parent.name == f".{app_name}"