diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 466536a8..1bb2d41a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,13 +9,16 @@ on: - main paths: - 'constructor-manager/**' - - 'constructor-manager-cli/**' + - 'constructor-manager-api/**' workflow_dispatch: jobs: test: name: ${{ matrix.platform }} py${{ matrix.python-version }} runs-on: ${{ matrix.platform }} + defaults: + run: + shell: bash -el {0} strategy: matrix: platform: [ubuntu-latest, windows-latest, macos-latest] @@ -25,36 +28,49 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: conda-incubator/setup-miniconda@v2 with: - python-version: ${{ matrix.python-version }} + activate-environment: "" + auto-activate-base: true - - name: Install dependencies constructor-manager-cli + - name: Install dependencies on main environment run: | - python -m pip install --upgrade pip - python -m pip install setuptools tox tox-gh-actions - cd constructor-manager-cli - pip install -e . - pip list + conda install -n base conda-lock mamba -y -c conda-forge + conda list - - name: Test constructor-manager-cli + - name: create constructor-manager-environment run: | - cd constructor-manager-cli - python -m tox - env: - PLATFORM: ${{ matrix.platform }} + conda create -n constructor-manager conda packaging requests pyyaml -y -c conda-forge + conda activate constructor-manager + # List installed packages + conda list - - name: Install dependencies constructor-manager + - name: Install constructor-manager run: | - cd constructor-manager-cli - pip install -e . - pip list - env: - PLATFORM: ${{ matrix.platform }} + conda activate constructor-manager + # Install constructor manager + git clone https://github.com/goanpeca/packaging.git packaging_clone + cd packaging_clone + git checkout constructor-updater + cd constructor-manager + pip install -e . --no-deps + # Install test deps + conda install -n constructor-manager pytest pytest-cov pytest-qt -c conda-forge -y + # List installed packages + conda list - - name: Test constructor-manager + - name: Install constructor-manager-api run: | - cd constructor-manager - python -m tox - env: - PLATFORM: ${{ matrix.platform }} + conda activate constructor-manager + conda install -n constructor-manager qtpy pyqt -y -c conda-forge + cd constructor-manager-api + pip install -e . --no-deps + + - name: Test constructor-manager-api + run: | + conda activate constructor-manager + # List installed packages + conda list + cd constructor-manager-api/src + # Run Tests + pytest constructor_manager_api --cov=constructor_manager_api diff --git a/constructor-manager/LICENSE b/constructor-manager-api/LICENSE similarity index 100% rename from constructor-manager/LICENSE rename to constructor-manager-api/LICENSE diff --git a/constructor-manager/MANIFEST.in b/constructor-manager-api/MANIFEST.in similarity index 100% rename from constructor-manager/MANIFEST.in rename to constructor-manager-api/MANIFEST.in diff --git a/constructor-manager-api/README.md b/constructor-manager-api/README.md new file mode 100644 index 00000000..3586b69c --- /dev/null +++ b/constructor-manager-api/README.md @@ -0,0 +1,64 @@ +# Constructor manager API + +## Requirements + +- qtpy +- constructor-manager-cli (on base environment) + +## Usage + +```python +from constructor_manager_api.api import check_updates + + +def finished(result): + print(result) + + +worker = check_updates(package_name="napari", current_version="0.4.10", channels=[]"conda-forge"]) +worker.finished.connect(finished) +worker.start() +``` + +## Other examples + +```python +import sys + +from qtpy.QtCore import QCoreApplication, QTimer # type: ignore + +from constructor_manager_api.api import open_manager + + +def _finished(res): + print("This is the result", res) + + +if __name__ == "__main__": + app = QCoreApplication([]) + + # Process the event loop + timer = QTimer() + timer.timeout.connect(lambda: None) # type: ignore + timer.start(100) + + worker = check_updates( + "napari", + current_version="0.4.15", + channel="napari", + dev=True, + ) + worker = check_updates( + "napari", + build_string="pyside", + plugins_url="https://api.napari-hub.org/plugins", + ) + worker = check_version("napari") + worker.finished.connect(_finished) + worker.start() + sys.exit(app.exec_()) +``` + +## License + +Distributed under the terms of the MIT license. is free and open source software diff --git a/constructor-manager/pyproject.toml b/constructor-manager-api/pyproject.toml similarity index 100% rename from constructor-manager/pyproject.toml rename to constructor-manager-api/pyproject.toml diff --git a/constructor-manager/setup.cfg b/constructor-manager-api/setup.cfg similarity index 81% rename from constructor-manager/setup.cfg rename to constructor-manager-api/setup.cfg index ff2e8e31..13818da1 100644 --- a/constructor-manager/setup.cfg +++ b/constructor-manager-api/setup.cfg @@ -1,12 +1,12 @@ [metadata] -name = constructor-manager -version = 0.0.1 -description = Constructor environment and updates manager API +name = constructor-manager-api +version = 0.1.0 +description = TODO long_description = file: README.md long_description_content_type = text/markdown -url = https://github.com/napari/packaging/constructor-manager -author = napari team -author_email = napari-steering-council@googlegroups.com +url = https://github.com/napari/packaging/constructor-manager-api +author = napari +author_email = TODO license = MIT license_files = LICENSE classifiers = @@ -23,11 +23,12 @@ classifiers = Topic :: Scientific/Engineering :: Image Processing project_urls = Bug Tracker = https://github.com/napari/packaging/issues - Source Code = https://github.com/napari/packaging/constructor-manager + Source Code = https://github.com/napari/packaging/constructor-manager-api [options] packages = find: install_requires = + pyyaml qtpy python_requires = >=3.8 include_package_data = True diff --git a/constructor-manager-api/src/constructor_manager_api/__init__.py b/constructor-manager-api/src/constructor_manager_api/__init__.py new file mode 100644 index 00000000..e9bca1b2 --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/__init__.py @@ -0,0 +1,2 @@ +VERSION_INFO = (0, 1, 0) +__version__ = "0.1.0" diff --git a/constructor-manager-api/src/constructor_manager_api/api.py b/constructor-manager-api/src/constructor_manager_api/api.py new file mode 100644 index 00000000..52fecfe7 --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/api.py @@ -0,0 +1,476 @@ +"""Constructor manager api.""" + +from enum import Enum +from typing import List, Optional +import logging + +from constructor_manager_api.defaults import DEFAULT_CHANNEL +from constructor_manager_api.utils.worker import ConstructorManagerWorker +from constructor_manager_api.utils.conda import get_prefix_by_name +from constructor_manager_api.utils.settings import save_settings + +from qtpy.QtCore import QProcess + + +logger = logging.getLogger(__name__) + + +class ActionsEnum(str, Enum): + check_updates = "check-updates" + check_version = "check-version" + check_packages = "check-packages" + update = "update" + lock_environment = "lock-environment" + restore = "restore" + revert = "revert" + reset = "reset" + open = "open" + + +def _run_action( + cmd: ActionsEnum, + package_name: Optional[str] = None, + version: Optional[str] = None, + build_string: Optional[str] = None, + channels: Optional[List[str]] = None, + dev: bool = False, + plugins_url: Optional[str] = None, + target_prefix: Optional[str] = None, + delayed: bool = False, + state: Optional[str] = None, +) -> ConstructorManagerWorker: + """Run constructor action. + + Parameters + ---------- + cmd : ActionsEnum + Action to run. + package_name : str, optional + Name of the package to execute action on. + version : str, optional + Version of package to execute action on, by default ``None``. + If not provided the latest version found will be used. + channels : list of str, optional + Channel to check for updates, by default ``[DEFAULT_CHANNEL]``. + plugins : List[str], optional + List of plugins to install, by default ``None``. + dev : bool, optional + Check for development version, by default ``False``. + target_prefix : str, optional + Target prefix to install package to, by default ``None``. + delayed : bool, optional + Delay execution of action, by default ``False``. + state : str, optional + State to restore, by default ``None``. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + args = [cmd.value] + if version is None: + version = "*" + + spec = f"{package_name}={version}" + + if build_string is not None: + spec += f"=*{build_string}*" + + if package_name is not None and version is not None: + args.extend([spec]) + if channels: + for channel in channels: + args.extend(["--channel", channel]) + + if plugins_url: + args.append("--plugins-url") + args.append(plugins_url) + + if dev: + args.extend(["--dev"]) + + if target_prefix: + args.append("--target-prefix") + args.append(target_prefix) + + if delayed: + args.append("--delayed") + + if state: + args.append("--state") + args.append(state) + + detached = cmd != "status" + detached = False + + log_level = logging.getLevelName(logger.getEffectiveLevel()) + args.extend(["--log", log_level]) + logger.debug("Running: constructor-manager %s", " ".join(args)) + return ConstructorManagerWorker(args, detached=detached) + + +def check_updates( + package_name: str, + current_version: Optional[str] = None, + build_string: Optional[str] = None, + channels: List[str] = [ + DEFAULT_CHANNEL, + ], + dev: bool = False, +) -> ConstructorManagerWorker: + """Check for updates. + + Parameters + ---------- + package_name : str + Name of the package to check for updates. + current_version : str, optional + Current version of the package. If ``None`` the latest version found + will be used. + build_string: str, optional + Build string of the package. + channels : list of str, optional + Channels to check for updates, by default ``[DEFAULT_CHANNEL]``. + dev : bool, optional + Check for development version, by default ``False``. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + return _run_action( + ActionsEnum.check_updates, + package_name, + version=current_version, + build_string=build_string, + channels=channels, + dev=dev, + ) + + +def check_version(package_name: str) -> ConstructorManagerWorker: + """Check for version. + + Parameters + ---------- + package_name : str + Name of the package to check for updates. + + Returns + ------- + ConstructorManagerWorker + Worker to check for the current version insytalled. + """ + return _run_action(ActionsEnum.check_version, package_name) + + +def check_packages( + package_name: str, + version: Optional[str] = None, + plugins_url: Optional[str] = None, +) -> ConstructorManagerWorker: + """Check for updates. + + Parameters + ---------- + package_name : str + Name of the package to check for updates. + version : str + Version of package to execute action on, by default ``None``. + plugins_url : str, optional + URL to plugins provided by the application as a list of dicts. + Keys will be used to filter out packages that are plugins of the + application. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + return _run_action( + ActionsEnum.check_packages, + package_name, + version=version, + plugins_url=plugins_url, + ) + + +def update( + package_name: str, + current_version: str, + build_string: Optional[str] = None, + channels: Optional[List[str]] = None, + plugins_url: Optional[str] = None, + dev: bool = False, + delayed: bool = False, +) -> ConstructorManagerWorker: + """Update the package to given version. + + If version is None update to latest version found. + + Parameters + ---------- + package_name : str + Name of the package to update. + current_version : str + Current version of the package to update. This is not the version to + update to. + build_string: str, optional + Build string of the package. For example `'*pyside*`'. + channels : list of str, optional + Conda channels to check for updates. + dev : bool, optional + Check for development versions of the package, by default ``False``. + delayed : bool, optional + Delay execution of action, by default ``False``. Useful when running + from the main application in the background, instead of using the + constructor manager UI. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + return _run_action( + ActionsEnum.update, + package_name, + current_version, + build_string=build_string, + channels=channels, + dev=dev, + delayed=delayed, + plugins_url=plugins_url, + ) + + +def restore( + package_name: str, + current_version: str, + state: str, + channels: Optional[List[str]] = None, + dev: bool = False, +) -> ConstructorManagerWorker: + """Restore to a given saved state within the current version. + + Parameters + --------- + package_name : str + Name of the package to check for updates. + current_version : str, optional + Version to rollback to, by default ``None``. + state : str + State to restore to. + channel : str, optional + Channel to check for updates, by default ``DEFAULT_CHANNEL``. + dev : bool, optional + Check for development versions, by default ``False``. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + return _run_action( + ActionsEnum.restore, + package_name, + version=current_version, + channels=channels, + dev=dev, + state=state, + ) + + +def revert( + package_name: str, + current_version: Optional[str], + channels: Optional[List[str]] = None, + dev: bool = False, +) -> ConstructorManagerWorker: + """Revert to a previous version state of the current package. + + Parameters + --------- + package_name : str + Name of the package to check for updates. + current_version : str, optional + Current version of the package to revert. + channels : str, optional + Channel to check for updates, by default ``DEFAULT_CHANNEL``. + dev : bool, optional + Check for development version, by default ``False``. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + return _run_action( + ActionsEnum.revert, + package_name, + version=current_version, + channels=channels, + dev=dev, + ) + + +def reset( + package_name: str, + current_version: Optional[str], + channels: Optional[List[str]] = None, + dev: bool = False, +) -> ConstructorManagerWorker: + """Reset an environment from scratch. + + This will remove all packages and reinstall the current version + of the package. + + Parameters + --------- + package_name : str + Name of the package to check for updates. + version : str, optional + Version to rollback to, by default ``None``. + channel : str, optional + Channel to check for updates, by default ``DEFAULT_CHANNEL``. + dev : bool, optional + Check for development version, by default ``False``. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + return _run_action( + ActionsEnum.reset, + package_name, + version=current_version, + channels=channels, + dev=dev, + ) + + +def lock_environment( + package_name: str, + current_version: Optional[str], + plugins_url: Optional[str] = None, +) -> ConstructorManagerWorker: + """Generate a lock state file using conda-lock for the environment + with package and version. + + This will generate a state file in the configuration folder, so that + the restore command can be used with these checkpoints. + + Parameters + --------- + package_name : str + Name of the package to check for updates. + current_version : str, optional + Version to rollback to, by default ``None``. + plugins_url : str, optional + URL to the plugins to install, by default ``None``. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + return _run_action( + ActionsEnum.lock_environment, + package_name, + version=current_version, + plugins_url=plugins_url, + ) + + +def open_manager( + package_name: str, + current_version: Optional[str] = None, + plugins_url: Optional[str] = None, + build_string: Optional[str] = None, + channels: Optional[List[str]] = None, + dev: bool = False, + log: Optional[str] = None, +) -> bool: + """ + Open the constructor manager. + + Parameters + ---------- + package_name : str + Name of the package to check for updates. + current_version : str, optional + Current version of the package. If ``None`` the latest version found + will be used. + build_string: str, optional + Build string of the package. + channels : list of str, optional + Channels to check for updates, by default ``None``. + dev : bool, optional + Check for development version, by default ``False``. + """ + envs = ["_constructor-manager", "constructor-manager", "base"] + for env in envs: + target_prefix = get_prefix_by_name(env) + path = target_prefix / "bin" / "constructor-manager-ui" + if path.exists(): + break + + settings = {} + + args = [package_name] + if current_version: + settings["current_version"] = current_version + + if plugins_url: + settings["plugins_url"] = plugins_url + + if build_string: + settings["build_string"] = build_string + + if dev: + settings["dev"] = dev # type: ignore + + if channels: + settings["channels"] = channels # type: ignore + + if log: + settings["log"] = log # type: ignore + + save_settings(package_name, settings) + # TODO: For a separate PR, use open_application when the convention + # for applications using constructor nanager is defined in the bundle + # workflow + return QProcess.startDetached( + str(path), + args, + ) + + +def open_application(package_name: str, version: str, target_prefix=None): + """Open the application name for given version in a specific prefix. + + Parameters + ---------- + package_name : str + Name of the package to check for updates. + version : str + Version to open. + target_prefix : str, optional + Target prefix to open the application in, by default ``None``. + """ + return _run_action( + ActionsEnum.open, + package_name, + version=version, + target_prefix=target_prefix, + ) diff --git a/constructor-manager-api/src/constructor_manager_api/defaults.py b/constructor-manager-api/src/constructor_manager_api/defaults.py new file mode 100644 index 00000000..de3e7b90 --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/defaults.py @@ -0,0 +1,3 @@ +"""Defaults and constants.""" + +DEFAULT_CHANNEL = "conda-forge" diff --git a/constructor-manager-api/src/constructor_manager_api/run.py b/constructor-manager-api/src/constructor_manager_api/run.py new file mode 100644 index 00000000..a6b7853d --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/run.py @@ -0,0 +1,45 @@ +"""Constructor updater api run tester.""" + +import sys + +from qtpy.QtCore import QCoreApplication, QTimer # type: ignore + +from constructor_manager_api.api import open_manager + + +def _finished(res): + print("This is the result", res) + + +if __name__ == "__main__": + app = QCoreApplication([]) + + # Process the event loop + timer = QTimer() + timer.timeout.connect(lambda: None) # type: ignore + timer.start(100) + + # worker = check_updates( + # "napari", + # current_version="0.4.15", + # channel="napari", + # dev=True, + # ) + # worker = check_updates( + # "napari", + # build_string="pyside", + # plugins_url="https://api.napari-hub.org/plugins", + # ) + # worker = check_version("napari") + # worker.finished.connect(_finished) + # worker.start() + + process = open_manager( + "napari", + build_string="pyside", + plugins_url="https://api.napari-hub.org/plugins", + channels=["napari", "conda-forge"], + log="DEBUG", + ) + sys.exit(process) + # sys.exit(app.exec_()) diff --git a/constructor-manager/src/constructor_manager/tests/test_main.py b/constructor-manager-api/src/constructor_manager_api/tests/test_main.py similarity index 50% rename from constructor-manager/src/constructor_manager/tests/test_main.py rename to constructor-manager-api/src/constructor_manager_api/tests/test_main.py index 488bd751..9433d6d4 100644 --- a/constructor-manager/src/constructor_manager/tests/test_main.py +++ b/constructor-manager-api/src/constructor_manager_api/tests/test_main.py @@ -1,7 +1,7 @@ """Constructor manager API.""" -import constructor_manager +import constructor_manager_api def test_constructor_manager(): - assert constructor_manager + assert constructor_manager_api diff --git a/constructor-manager-api/src/constructor_manager_api/utils/__init__.py b/constructor-manager-api/src/constructor_manager_api/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/constructor-manager-api/src/constructor_manager_api/utils/_tests/test_worker.py b/constructor-manager-api/src/constructor_manager_api/utils/_tests/test_worker.py new file mode 100644 index 00000000..bb4feed7 --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/utils/_tests/test_worker.py @@ -0,0 +1,5 @@ +from constructor_manager_api.utils.conda import get_base_prefix + + +def test_worker(): + assert get_base_prefix() diff --git a/constructor-manager-api/src/constructor_manager_api/utils/conda.py b/constructor-manager-api/src/constructor_manager_api/utils/conda.py new file mode 100644 index 00000000..f0ed45cf --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/utils/conda.py @@ -0,0 +1,47 @@ +"""Conda utilities.""" + +import sys +from pathlib import Path +from typing import Optional + + +def get_base_prefix() -> Path: + """Get base conda prefix. + + Returns + ------- + pathlib.Path + Base conda prefix. + """ + current = Path(sys.prefix) + if (current / "envs").exists() and (current / "envs").is_dir(): + return current + + if current.parent.name == "envs" and current.parent.is_dir(): + return current.parent.parent + + return current + + +def get_prefix_by_name(name: Optional[str] = None) -> Path: + """Get conda prefix by environment name. + + This does not check if the environment exists. + + Parameters + ---------- + name : str, optional + Name of the environment. If `None` then return the current prefix. + + Returns + ------- + pathlib.Path + Conda prefix for the corresponding ``name``. + """ + base_prefix = get_base_prefix() + if name is None: + return Path(sys.prefix) + elif name == "base": + return base_prefix + else: + return base_prefix / "envs" / name diff --git a/constructor-manager-api/src/constructor_manager_api/utils/io.py b/constructor-manager-api/src/constructor_manager_api/utils/io.py new file mode 100644 index 00000000..ee1fb7df --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/utils/io.py @@ -0,0 +1,17 @@ +from pathlib import Path + +from constructor_manager_api.utils.conda import get_prefix_by_name + + +def get_config_path() -> Path: + """Get the path to the constructor-manager-ui config directory.""" + path = get_prefix_by_name("constructor-manager") / "var" / "constructor-manager-ui" + path.mkdir(parents=True, exist_ok=True) + return path + + +def get_settings_path(package_name: str) -> Path: + """Get the path to the settings file for a package.""" + path = get_config_path() / "settings" / f"{package_name}.yaml" + path.parent.mkdir(parents=True, exist_ok=True) + return path diff --git a/constructor-manager-api/src/constructor_manager_api/utils/settings.py b/constructor-manager-api/src/constructor_manager_api/utils/settings.py new file mode 100644 index 00000000..da49bdba --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/utils/settings.py @@ -0,0 +1,36 @@ +"""Constuctor manager.""" + +from typing import Any, Dict + +import yaml + +from constructor_manager_api.utils.io import get_settings_path + + +_default_settings = { # type: ignore + "current_version": None, + "build_string": None, + "plugins_url": None, + "channels": [], + "log": None, + "dev": None, +} + + +def save_settings(package_name: str, settings: Dict[str, Any]) -> None: + """"Save constructor manager settings to file per `package_name`.""" "" + path = get_settings_path(package_name) + with open(path, "w") as f: + yaml.dump(settings, f) + + +def load_settings(package_name: str): + """"Load constructor manager settings from file per `package_name`.""" "" + path = get_settings_path(package_name) + loaded_settings = _default_settings.copy() + if path.exists(): + with open(path) as f: + data = yaml.load(f, Loader=yaml.FullLoader) + loaded_settings.update(data) + + return loaded_settings diff --git a/constructor-manager-api/src/constructor_manager_api/utils/worker.py b/constructor-manager-api/src/constructor_manager_api/utils/worker.py new file mode 100644 index 00000000..6b09f88f --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/utils/worker.py @@ -0,0 +1,110 @@ +"""Constructor updater api worker.""" + +import json +import logging +from typing import List + +from qtpy.QtCore import QObject, QProcess, Signal # type: ignore + +from constructor_manager_api.utils.conda import get_prefix_by_name + + +logger = logging.getLogger(__name__) + + +class ConstructorManagerWorker(QObject): + """A worker to run the constructor manager cli and process errors. + + Parameters + ---------- + args : list + Arguments to pass to the constructor manager. + detached : bool, optional + Run the process detached, by default ``False``. + """ + + _WORKERS: "List[ConstructorManagerWorker]" = [] + finished = Signal(dict) + + def __init__(self, args, detached=False): + super().__init__() + ConstructorManagerWorker._WORKERS.append(self) + self._detached = detached + self._program = self._executable() + + if not self._program.is_file(): + raise FileNotFoundError(f"Could not find {self._program}") + + self._args = args + self._process = QProcess() + self._process.setArguments(args) + self._process.setProgram(str(self._program)) + self._process.finished.connect(self._finished) + + @staticmethod + def _executable(): + """Get the executable for the constructor manager.""" + bin = "constructor-manager-cli" + envs = ["_constructor-manager", "constructor-manager", "base"] + for env in envs: + program = get_prefix_by_name(env) / "bin" / bin + if program.is_file(): + return program + else: + raise FileNotFoundError( + f"Could not find {bin} in any of the following environments: {envs}" + ) + + def _finished( + self, + exit_code: int, + exit_status: QProcess.ExitStatus = QProcess.ExitStatus.NormalExit, + ): + """Handle the finished signal of the worker and emit results.""" + logger.debug( + "Worker with args `%s` finished with exit code %s and exit status %s", + " ".join(self._args), + exit_code, + exit_status, + ) + try: + stdout = self._process.readAllStandardOutput() + stderr = self._process.readAllStandardError() + except RuntimeError as e: + self.finished.emit({"data": {}, "error": str(e)}) + return + + raw_output = stdout.data().decode() + raw_error = stderr.data().decode() + error = raw_error + + output = {} + if exit_code == 0 and exit_status == QProcess.ExitStatus.NormalExit: + try: + output = json.loads(raw_output) + except Exception: + error = raw_output + + data = output.get("data", raw_output) + error = error or output.get("error", error) + result = { + "data": data, + "error": error, + "exit_code": exit_code, + "exit_status": exit_status, + } + self.finished.emit(result) + + def state(self): + """State of the worker.""" + return self._process.state() + + def start(self): + """Start the worker.""" + logger.debug("Worker with args `%s` started!", " ".join(self._args)) + self._process.startDetached() if self._detached else self._process.start() + + def terminate(self): + """Terminate the process worker.""" + logger.debug("Worker with args `%s` terminated!", " ".join(self._args)) + self._process.terminate() diff --git a/constructor-manager/tox.ini b/constructor-manager-api/tox.ini similarity index 95% rename from constructor-manager/tox.ini rename to constructor-manager-api/tox.ini index b1f09aa6..f73994ab 100644 --- a/constructor-manager/tox.ini +++ b/constructor-manager-api/tox.ini @@ -23,7 +23,8 @@ platform = passenv = CI GITHUB_ACTIONS - DISPLAY XAUTHORITY + DISPLAY + XAUTHORITY NUMPY_EXPERIMENTAL_ARRAY_FUNCTION PYVISTA_OFF_SCREEN extras = diff --git a/constructor-manager/README.md b/constructor-manager/README.md deleted file mode 100644 index 96c8c0b9..00000000 --- a/constructor-manager/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Constructor manager - -TODO diff --git a/constructor-manager/src/constructor_manager/__init__.py b/constructor-manager/src/constructor_manager/__init__.py deleted file mode 100644 index d05157c8..00000000 --- a/constructor-manager/src/constructor_manager/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Constructor manager API.""" - -__version__ = "0.0.1"