From f41c905d554954168c2682a8b3b9874246634de9 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 28 Nov 2022 23:39:36 -0500 Subject: [PATCH 01/17] Add constructor manager CLI --- constructor-manager/README.md | 24 ++- constructor-manager/setup.cfg | 8 +- .../src/constructor_manager/__init__.py | 5 +- .../src/constructor_manager/api.py | 182 ++++++++++++++++++ .../src/constructor_manager/defaults.py | 3 + .../src/constructor_manager/run.py | 32 +++ .../src/constructor_manager/utils/__init__.py | 2 + .../utils/_tests/test_worker.py | 5 + .../src/constructor_manager/utils/conda.py | 22 +++ .../src/constructor_manager/utils/worker.py | 55 ++++++ 10 files changed, 330 insertions(+), 8 deletions(-) create mode 100644 constructor-manager/src/constructor_manager/api.py create mode 100644 constructor-manager/src/constructor_manager/defaults.py create mode 100644 constructor-manager/src/constructor_manager/run.py create mode 100644 constructor-manager/src/constructor_manager/utils/__init__.py create mode 100644 constructor-manager/src/constructor_manager/utils/_tests/test_worker.py create mode 100644 constructor-manager/src/constructor_manager/utils/conda.py create mode 100644 constructor-manager/src/constructor_manager/utils/worker.py diff --git a/constructor-manager/README.md b/constructor-manager/README.md index 96c8c0b9..50d15284 100644 --- a/constructor-manager/README.md +++ b/constructor-manager/README.md @@ -1,3 +1,25 @@ # Constructor manager -TODO +## Requirements + +- qtpy +- constructor-manager-cli (on base environment) + +## Usage + +```python +from constructor_manager.api import check_updates + + +def finished(result): + print(result) + + +worker = check_updates(package_name="napari", current_version="0.4.10", channel="conda-forge") +worker.finished.connect(finished) +worker.start() +``` + +## License + +Distributed under the terms of the MIT license. is free and open source software diff --git a/constructor-manager/setup.cfg b/constructor-manager/setup.cfg index ff2e8e31..d204106b 100644 --- a/constructor-manager/setup.cfg +++ b/constructor-manager/setup.cfg @@ -1,12 +1,12 @@ [metadata] name = constructor-manager -version = 0.0.1 -description = Constructor environment and updates 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 +author = napari +author_email = TODO license = MIT license_files = LICENSE classifiers = diff --git a/constructor-manager/src/constructor_manager/__init__.py b/constructor-manager/src/constructor_manager/__init__.py index d05157c8..e9bca1b2 100644 --- a/constructor-manager/src/constructor_manager/__init__.py +++ b/constructor-manager/src/constructor_manager/__init__.py @@ -1,3 +1,2 @@ -"""Constructor manager API.""" - -__version__ = "0.0.1" +VERSION_INFO = (0, 1, 0) +__version__ = "0.1.0" diff --git a/constructor-manager/src/constructor_manager/api.py b/constructor-manager/src/constructor_manager/api.py new file mode 100644 index 00000000..91f3ea85 --- /dev/null +++ b/constructor-manager/src/constructor_manager/api.py @@ -0,0 +1,182 @@ +"""Constructor manager api.""" + +from typing import List, Optional + +from constructor_manager.defaults import DEFAULT_CHANNEL +from constructor_manager.utils.worker import ConstructorManagerWorker + + +def _run_action( + cmd, + package_name: Optional[str] = None, + version: Optional[str] = None, + channel: str = DEFAULT_CHANNEL, + plugins: Optional[List[str]] = None, + dev: bool = False, +) -> ConstructorManagerWorker: + """Run constructor action. + + Parameters + ---------- + cmd : str + 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``. + channel : 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``. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + args = [cmd] + if package_name is not None and version is not None: + spec: Optional[str] = f"{package_name}={version}" + else: + spec = package_name + + if package_name is not None and version is not None: + args.extend([spec, "--channel", channel]) + + if plugins: + args.append("--plugins") + args.extend(plugins) + + if dev: + args.extend(["--dev"]) + + detached = cmd != "status" + return ConstructorManagerWorker(args, detached=detached) + + +def check_updates( + package_name, current_version, channel: 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 + Current version of the package. + 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( + "check-updates", package_name, version=current_version, channel=channel, dev=dev + ) + + +def update( + package_name, + channel: str = DEFAULT_CHANNEL, + plugins: Optional[List[str]] = None, + dev: bool = False, +) -> ConstructorManagerWorker: + """Update the package to given version. + If version is None update to latest version found. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + return _run_action( + "update", package_name, channel=channel, plugins=plugins, dev=dev + ) + + +def rollback( + package_name, + current_version: Optional[str], + channel: str = DEFAULT_CHANNEL, + plugins: Optional[List[str]] = None, + dev: 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 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( + "rollback", + package_name, + version=current_version, + channel=channel, + plugins=plugins, + dev=dev, + ) + + +def restore( + package_name, + version, + channel: str = DEFAULT_CHANNEL, + dev: bool = False, + plugins: Optional[List[str]] = None, +) -> ConstructorManagerWorker: + """Restore the current version of 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 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( + "restore", + package_name, + version=version, + channel=channel, + plugins=plugins, + dev=dev, + ) + + +def status(): + """Get status for the state of the constructor updater.""" + return _run_action("status") diff --git a/constructor-manager/src/constructor_manager/defaults.py b/constructor-manager/src/constructor_manager/defaults.py new file mode 100644 index 00000000..de3e7b90 --- /dev/null +++ b/constructor-manager/src/constructor_manager/defaults.py @@ -0,0 +1,3 @@ +"""Defaults and constants.""" + +DEFAULT_CHANNEL = "conda-forge" diff --git a/constructor-manager/src/constructor_manager/run.py b/constructor-manager/src/constructor_manager/run.py new file mode 100644 index 00000000..aeeeecf0 --- /dev/null +++ b/constructor-manager/src/constructor_manager/run.py @@ -0,0 +1,32 @@ +"""Constructor updater api run tester.""" + +import sys + +from qtpy.QtCore import QCoreApplication, QTimer # type: ignore + +from constructor_manager.api import check_updates + + +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", current_version="0.4.15") + worker.finished.connect(_finished) + worker.start() + + sys.exit(app.exec_()) diff --git a/constructor-manager/src/constructor_manager/utils/__init__.py b/constructor-manager/src/constructor_manager/utils/__init__.py new file mode 100644 index 00000000..e9bca1b2 --- /dev/null +++ b/constructor-manager/src/constructor_manager/utils/__init__.py @@ -0,0 +1,2 @@ +VERSION_INFO = (0, 1, 0) +__version__ = "0.1.0" diff --git a/constructor-manager/src/constructor_manager/utils/_tests/test_worker.py b/constructor-manager/src/constructor_manager/utils/_tests/test_worker.py new file mode 100644 index 00000000..94027677 --- /dev/null +++ b/constructor-manager/src/constructor_manager/utils/_tests/test_worker.py @@ -0,0 +1,5 @@ +from constructor_manager.utils.conda import get_base_prefix + + +def test_worker(): + assert get_base_prefix() diff --git a/constructor-manager/src/constructor_manager/utils/conda.py b/constructor-manager/src/constructor_manager/utils/conda.py new file mode 100644 index 00000000..b11006bf --- /dev/null +++ b/constructor-manager/src/constructor_manager/utils/conda.py @@ -0,0 +1,22 @@ +"""Conda utilities.""" + +import sys +from pathlib import Path + + +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 diff --git a/constructor-manager/src/constructor_manager/utils/worker.py b/constructor-manager/src/constructor_manager/utils/worker.py new file mode 100644 index 00000000..ca9d6ac0 --- /dev/null +++ b/constructor-manager/src/constructor_manager/utils/worker.py @@ -0,0 +1,55 @@ +"""Constructor updater api worker.""" + +import json + +from qtpy.QtCore import QObject, QProcess, Signal # type: ignore + +from constructor_manager.utils.conda import get_base_prefix + + +class ConstructorManagerWorker(QObject): + """TODO: + + Parameters + ---------- + args : list + Arguments to pass to the constructor manager. + detached : bool, optional + Run the process detached, by default ``False``. + """ + + finished = Signal(dict) + + def __init__(self, args, detached=False): + super().__init__() + self._detached = detached + self._program = get_base_prefix() / "bin" / "constructor-manager" + + if not self._program.is_file(): + raise FileNotFoundError(f"Could not find {self._program}") + + self._process = QProcess() + self._process.setArguments(args) + self._process.setProgram(str(self._program)) + self._process.finished.connect(self._finished) + + def _finished(self, *args, **kwargs): + """Handle the finished signal of the worker and emit results.""" + stdout = self._process.readAllStandardOutput() + stderr = self._process.readAllStandardError() + data = stdout.data().decode() + error = stderr.data().decode() + try: + data = json.loads(data) + except Exception as e: + print(e) + + result = {"data": data, "error": error} + self.finished.emit(result) + + def start(self): + """Start the worker.""" + if self._detached: + self._process.startDetached() + else: + self._process.start() From 2f76bcd8314a9728e08f0494e3a8986eabd7540f Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Thu, 26 Jan 2023 16:06:43 -0500 Subject: [PATCH 02/17] Update API to match backend and add helper method to open constructor manager ui --- .../src/constructor_manager/api.py | 169 ++++++++++++++---- .../src/constructor_manager/run.py | 11 +- .../src/constructor_manager/utils/worker.py | 17 +- 3 files changed, 160 insertions(+), 37 deletions(-) diff --git a/constructor-manager/src/constructor_manager/api.py b/constructor-manager/src/constructor_manager/api.py index 91f3ea85..d64b51c3 100644 --- a/constructor-manager/src/constructor_manager/api.py +++ b/constructor-manager/src/constructor_manager/api.py @@ -1,18 +1,23 @@ """Constructor manager api.""" - +import sys +from pathlib import Path from typing import List, Optional from constructor_manager.defaults import DEFAULT_CHANNEL from constructor_manager.utils.worker import ConstructorManagerWorker +from constructor_manager.utils.conda import get_base_prefix + +from qtpy.QtCore import QProcess def _run_action( cmd, package_name: Optional[str] = None, version: Optional[str] = None, - channel: str = DEFAULT_CHANNEL, - plugins: Optional[List[str]] = None, + build_string: Optional[str] = None, + channels: Optional[List[str]] = None, dev: bool = False, + plugins_url: Optional[str] = None, ) -> ConstructorManagerWorker: """Run constructor action. @@ -24,8 +29,9 @@ def _run_action( Name of the package to execute action on. version : str, optional Version of package to execute action on, by default ``None``. - channel : str, optional - Channel to check for updates, by default ``DEFAULT_CHANNEL``. + 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 @@ -38,27 +44,39 @@ def _run_action( a ``dict`` with the result. """ args = [cmd] - if package_name is not None and version is not None: - spec: Optional[str] = f"{package_name}={version}" - else: - spec = package_name + 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, "--channel", channel]) + args.extend([spec]) + if channels: + for channel in channels: + args.extend(["--channel", channel]) - if plugins: - args.append("--plugins") - args.extend(plugins) + if plugins_url: + args.append("--plugins-url") + args.append(plugins_url) if dev: args.extend(["--dev"]) detached = cmd != "status" + detached = False + print(args) return ConstructorManagerWorker(args, detached=detached) def check_updates( - package_name, current_version, channel: str = DEFAULT_CHANNEL, dev: bool = False + 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. @@ -66,10 +84,13 @@ def check_updates( ---------- package_name : str Name of the package to check for updates. - current_version : str - Current version of the package. - channel : str, optional - Channel to check for updates, by default ``DEFAULT_CHANNEL``. + 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``. @@ -80,14 +101,52 @@ def check_updates( a ``dict`` with the result. """ return _run_action( - "check-updates", package_name, version=current_version, channel=channel, dev=dev + "check-updates", + package_name, + version=current_version, + build_string=build_string, + channels=channels, + dev=dev, ) +def check_version(package_name: str,) -> ConstructorManagerWorker: + """Check for updates. + + Parameters + ---------- + package_name : str + Name of the package to check for updates. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + return _run_action("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. + + Returns + ------- + ConstructorManagerWorker + Worker to check for updates. Includes a finished signal that returns + a ``dict`` with the result. + """ + return _run_action("check-packages", package_name, version=version, plugins_url=plugins_url) + + def update( package_name, - channel: str = DEFAULT_CHANNEL, - plugins: Optional[List[str]] = None, + channels: Optional[List[str]] = None, dev: bool = False, ) -> ConstructorManagerWorker: """Update the package to given version. @@ -100,15 +159,14 @@ def update( a ``dict`` with the result. """ return _run_action( - "update", package_name, channel=channel, plugins=plugins, dev=dev + "update", package_name, channels=channels, dev=dev ) def rollback( package_name, current_version: Optional[str], - channel: str = DEFAULT_CHANNEL, - plugins: Optional[List[str]] = None, + channels: Optional[List[str]] = None, dev: bool = False, ) -> ConstructorManagerWorker: """Update the package to given version. @@ -135,8 +193,7 @@ def rollback( "rollback", package_name, version=current_version, - channel=channel, - plugins=plugins, + channels=channels, dev=dev, ) @@ -144,9 +201,8 @@ def rollback( def restore( package_name, version, - channel: str = DEFAULT_CHANNEL, + channels: Optional[List[str]] = None, dev: bool = False, - plugins: Optional[List[str]] = None, ) -> ConstructorManagerWorker: """Restore the current version of package. @@ -171,8 +227,7 @@ def restore( "restore", package_name, version=version, - channel=channel, - plugins=plugins, + channels=channels, dev=dev, ) @@ -180,3 +235,57 @@ def restore( def status(): """Get status for the state of the constructor updater.""" return _run_action("status") + + +def open_manager( + package_name, + current_version: Optional[str] = None, + plugins_url: Optional[str] = None, + build_string: Optional[str] = None, + channels: Optional[List[str]] = None, + dev: bool = False, + ) -> None: + """ + 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``. + """ + path = ( + get_base_prefix() + / "bin" + / "constructor-manager-ui" + ) + + args = [package_name] + if current_version: + args.extend(['--current-version', current_version]) + + if plugins_url: + args.extend(['--plugins-url', plugins_url]) + + if build_string: + args.extend(['--build-string', build_string]) + + if dev: + args.extend(['--dev']) + + if channels: + for channel in channels: + args.extend(['--channel', channel]) + + QProcess.startDetached( + str(path), + args + ) diff --git a/constructor-manager/src/constructor_manager/run.py b/constructor-manager/src/constructor_manager/run.py index aeeeecf0..4a982b82 100644 --- a/constructor-manager/src/constructor_manager/run.py +++ b/constructor-manager/src/constructor_manager/run.py @@ -4,7 +4,7 @@ from qtpy.QtCore import QCoreApplication, QTimer # type: ignore -from constructor_manager.api import check_updates +from constructor_manager.api import check_updates, check_version, open_manager def _finished(res): @@ -25,8 +25,11 @@ def _finished(res): # channel="napari", # dev=True, # ) - worker = check_updates("napari", current_version="0.4.15") - worker.finished.connect(_finished) - worker.start() + # 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() + + open_manager("napari", build_string="pyside", plugins_url="https://api.napari-hub.org/plugins") sys.exit(app.exec_()) diff --git a/constructor-manager/src/constructor_manager/utils/worker.py b/constructor-manager/src/constructor_manager/utils/worker.py index ca9d6ac0..0ab71591 100644 --- a/constructor-manager/src/constructor_manager/utils/worker.py +++ b/constructor-manager/src/constructor_manager/utils/worker.py @@ -35,14 +35,20 @@ def __init__(self, args, detached=False): def _finished(self, *args, **kwargs): """Handle the finished signal of the worker and emit results.""" - stdout = self._process.readAllStandardOutput() - stderr = self._process.readAllStandardError() + try: + stdout = self._process.readAllStandardOutput() + stderr = self._process.readAllStandardError() + except RuntimeError as e: + self.finished.emit({"data": {}, "error": str(e)}) + return + data = stdout.data().decode() error = stderr.data().decode() try: data = json.loads(data) except Exception as e: - print(e) + data = {} + # print(e) result = {"data": data, "error": error} self.finished.emit(result) @@ -53,3 +59,8 @@ def start(self): self._process.startDetached() else: self._process.start() + + def terminate(self): + """Terminate the process worker.""" + self._process.terminate() + self._process.waitForFinished() From 0c20d70e12ed8660e9933a7366caa1ca16462626 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Tue, 7 Feb 2023 10:09:51 -0500 Subject: [PATCH 03/17] Update API handling and open commands, add logging --- .../src/constructor_manager/api.py | 88 ++++++++++++++++--- .../src/constructor_manager/run.py | 4 +- .../src/constructor_manager/utils/worker.py | 48 ++++++---- 3 files changed, 110 insertions(+), 30 deletions(-) diff --git a/constructor-manager/src/constructor_manager/api.py b/constructor-manager/src/constructor_manager/api.py index d64b51c3..416ef972 100644 --- a/constructor-manager/src/constructor_manager/api.py +++ b/constructor-manager/src/constructor_manager/api.py @@ -1,7 +1,7 @@ """Constructor manager api.""" -import sys -from pathlib import Path + from typing import List, Optional +import logging from constructor_manager.defaults import DEFAULT_CHANNEL from constructor_manager.utils.worker import ConstructorManagerWorker @@ -10,6 +10,9 @@ from qtpy.QtCore import QProcess +logger = logging.getLogger(__name__) + + def _run_action( cmd, package_name: Optional[str] = None, @@ -67,7 +70,10 @@ def _run_action( detached = cmd != "status" detached = False - print(args) + + log_level = logging.getLevelName(logger.getEffectiveLevel()) + args.extend(["--log", log_level]) + logger.debug("Running: constructor-manager %s", " ".join(args)) return ConstructorManagerWorker(args, detached=detached) @@ -163,7 +169,41 @@ def update( ) -def rollback( +def restore( + package_name, + version, + channels: Optional[List[str]] = None, + dev: bool = False, +) -> ConstructorManagerWorker: + """Restore the current version of 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 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( + "restore", + package_name, + version=version, + channels=channels, + dev=dev, + ) + + +def revert( package_name, current_version: Optional[str], channels: Optional[List[str]] = None, @@ -198,13 +238,14 @@ def rollback( ) -def restore( +def reset( package_name, - version, + current_version: Optional[str], channels: Optional[List[str]] = None, dev: bool = False, ) -> ConstructorManagerWorker: - """Restore the current version of package. + """Update the package to given version. + If version is None update to latest version found. Parameters --------- @@ -215,7 +256,7 @@ def restore( channel : str, optional Channel to check for updates, by default ``DEFAULT_CHANNEL``. dev : bool, optional - Check for development versions, by default ``False``. + Check for development version, by default ``False``. Returns ------- @@ -224,15 +265,15 @@ def restore( a ``dict`` with the result. """ return _run_action( - "restore", + "reset", package_name, - version=version, + version=current_version, channels=channels, dev=dev, ) -def status(): +def get_status(): """Get status for the state of the constructor updater.""" return _run_action("status") @@ -244,7 +285,7 @@ def open_manager( build_string: Optional[str] = None, channels: Optional[List[str]] = None, dev: bool = False, - ) -> None: + ) -> QProcess: """ Open the constructor manager. @@ -285,7 +326,28 @@ def open_manager( for channel in channels: args.extend(['--channel', channel]) - QProcess.startDetached( + process = QProcess() + process.start( str(path), args ) + return process + + +def open_application(package_name, version): + return _run_action( + "open", + package_name, + version=version, + ) + + +def check_constructor_manager_updates(): + # TODO: Use a separate worker and call conda/mamba directly! + # this could use the same worker and run through constructor-manager-cli + pass + + +def update_constructor_manager(): + # Use a separate worker and call conda/mamba directly! + pass diff --git a/constructor-manager/src/constructor_manager/run.py b/constructor-manager/src/constructor_manager/run.py index 4a982b82..60605b05 100644 --- a/constructor-manager/src/constructor_manager/run.py +++ b/constructor-manager/src/constructor_manager/run.py @@ -30,6 +30,6 @@ def _finished(res): # worker.finished.connect(_finished) # worker.start() - open_manager("napari", build_string="pyside", plugins_url="https://api.napari-hub.org/plugins") - + process = open_manager("napari", build_string="pyside", plugins_url="https://api.napari-hub.org/plugins") + process.finished.connect(lambda: sys.exit(0)) sys.exit(app.exec_()) diff --git a/constructor-manager/src/constructor_manager/utils/worker.py b/constructor-manager/src/constructor_manager/utils/worker.py index 0ab71591..5e26585e 100644 --- a/constructor-manager/src/constructor_manager/utils/worker.py +++ b/constructor-manager/src/constructor_manager/utils/worker.py @@ -1,14 +1,18 @@ """Constructor updater api worker.""" import json +import logging from qtpy.QtCore import QObject, QProcess, Signal # type: ignore from constructor_manager.utils.conda import get_base_prefix +logger = logging.getLogger(__name__) + + class ConstructorManagerWorker(QObject): - """TODO: + """A worker to run the constructor manager cli and process errors. Parameters ---------- @@ -18,49 +22,63 @@ class ConstructorManagerWorker(QObject): Run the process detached, by default ``False``. """ + _WORKERS: 'ConstructorManagerWorker' = [] finished = Signal(dict) def __init__(self, args, detached=False): super().__init__() + ConstructorManagerWorker._WORKERS.append(self) self._detached = detached self._program = get_base_prefix() / "bin" / "constructor-manager" if not self._program.is_file(): raise FileNotFoundError(f"Could not find {self._program}") + # TODO: Environemnt variables? + self._args = args self._process = QProcess() self._process.setArguments(args) self._process.setProgram(str(self._program)) self._process.finished.connect(self._finished) - def _finished(self, *args, **kwargs): + 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: + # TODO: Fix this? self.finished.emit({"data": {}, "error": str(e)}) return - data = stdout.data().decode() - error = stderr.data().decode() - try: - data = json.loads(data) - except Exception as e: - data = {} - # print(e) + # TODO: Ensure to have the proper propagation of issues + raw_output = stdout.data().decode() + raw_error = stderr.data().decode() + error = raw_error - result = {"data": data, "error": error} + output = {} + if exit_code == 0 and exit_status == QProcess.ExitStatus.NormalExit: + try: + output = json.loads(raw_output) + except Exception as e: + 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.""" - if self._detached: - self._process.startDetached() - else: - self._process.start() + 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() - self._process.waitForFinished() From 016165b27c0a9216ce6b5a931053ea0fe28c0f76 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Tue, 7 Mar 2023 19:16:32 -0500 Subject: [PATCH 04/17] Rename package --- .../LICENSE | 0 .../MANIFEST.in | 0 .../README.md | 2 +- .../pyproject.toml | 0 .../setup.cfg | 6 +-- .../src/constructor_manager_api}/__init__.py | 2 + .../src/constructor_manager_api}/api.py | 6 +-- .../src/constructor_manager_api}/defaults.py | 0 .../src/constructor_manager_api}/run.py | 2 +- .../tests/test_main.py | 4 +- .../constructor_manager_api/utils/__init__.py | 0 .../utils/_tests/test_worker.py | 5 ++ .../constructor_manager_api/utils/conda.py | 47 +++++++++++++++++++ .../constructor_manager_api}/utils/worker.py | 19 ++++++-- .../tox.ini | 0 .../src/constructor_manager/utils/__init__.py | 2 - .../utils/_tests/test_worker.py | 5 -- .../src/constructor_manager/utils/conda.py | 22 --------- 18 files changed, 80 insertions(+), 42 deletions(-) rename {constructor-manager => constructor-manager-api}/LICENSE (100%) rename {constructor-manager => constructor-manager-api}/MANIFEST.in (100%) rename {constructor-manager => constructor-manager-api}/README.md (94%) rename {constructor-manager => constructor-manager-api}/pyproject.toml (100%) rename {constructor-manager => constructor-manager-api}/setup.cfg (91%) rename {constructor-manager/src/constructor_manager => constructor-manager-api/src/constructor_manager_api}/__init__.py (52%) rename {constructor-manager/src/constructor_manager => constructor-manager-api/src/constructor_manager_api}/api.py (97%) rename {constructor-manager/src/constructor_manager => constructor-manager-api/src/constructor_manager_api}/defaults.py (100%) rename {constructor-manager/src/constructor_manager => constructor-manager-api/src/constructor_manager_api}/run.py (91%) rename {constructor-manager/src/constructor_manager => constructor-manager-api/src/constructor_manager_api}/tests/test_main.py (50%) create mode 100644 constructor-manager-api/src/constructor_manager_api/utils/__init__.py create mode 100644 constructor-manager-api/src/constructor_manager_api/utils/_tests/test_worker.py create mode 100644 constructor-manager-api/src/constructor_manager_api/utils/conda.py rename {constructor-manager/src/constructor_manager => constructor-manager-api/src/constructor_manager_api}/utils/worker.py (80%) rename {constructor-manager => constructor-manager-api}/tox.ini (100%) delete mode 100644 constructor-manager/src/constructor_manager/utils/__init__.py delete mode 100644 constructor-manager/src/constructor_manager/utils/_tests/test_worker.py delete mode 100644 constructor-manager/src/constructor_manager/utils/conda.py 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/README.md b/constructor-manager-api/README.md similarity index 94% rename from constructor-manager/README.md rename to constructor-manager-api/README.md index 50d15284..8b97eef5 100644 --- a/constructor-manager/README.md +++ b/constructor-manager-api/README.md @@ -1,4 +1,4 @@ -# Constructor manager +# Constructor manager API ## Requirements 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 91% rename from constructor-manager/setup.cfg rename to constructor-manager-api/setup.cfg index d204106b..afc02d79 100644 --- a/constructor-manager/setup.cfg +++ b/constructor-manager-api/setup.cfg @@ -1,10 +1,10 @@ [metadata] -name = constructor-manager +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 +url = https://github.com/napari/packaging/constructor-manager-api author = napari author_email = TODO license = MIT @@ -23,7 +23,7 @@ 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: diff --git a/constructor-manager/src/constructor_manager/__init__.py b/constructor-manager-api/src/constructor_manager_api/__init__.py similarity index 52% rename from constructor-manager/src/constructor_manager/__init__.py rename to constructor-manager-api/src/constructor_manager_api/__init__.py index e9bca1b2..db7f0992 100644 --- a/constructor-manager/src/constructor_manager/__init__.py +++ b/constructor-manager-api/src/constructor_manager_api/__init__.py @@ -1,2 +1,4 @@ +from constructor_manager_api.api import * + VERSION_INFO = (0, 1, 0) __version__ = "0.1.0" diff --git a/constructor-manager/src/constructor_manager/api.py b/constructor-manager-api/src/constructor_manager_api/api.py similarity index 97% rename from constructor-manager/src/constructor_manager/api.py rename to constructor-manager-api/src/constructor_manager_api/api.py index 416ef972..45614e02 100644 --- a/constructor-manager/src/constructor_manager/api.py +++ b/constructor-manager-api/src/constructor_manager_api/api.py @@ -3,9 +3,9 @@ from typing import List, Optional import logging -from constructor_manager.defaults import DEFAULT_CHANNEL -from constructor_manager.utils.worker import ConstructorManagerWorker -from constructor_manager.utils.conda import get_base_prefix +from constructor_manager_api.defaults import DEFAULT_CHANNEL +from constructor_manager_api.utils.worker import ConstructorManagerWorker +from constructor_manager_api.utils.conda import get_base_prefix from qtpy.QtCore import QProcess diff --git a/constructor-manager/src/constructor_manager/defaults.py b/constructor-manager-api/src/constructor_manager_api/defaults.py similarity index 100% rename from constructor-manager/src/constructor_manager/defaults.py rename to constructor-manager-api/src/constructor_manager_api/defaults.py diff --git a/constructor-manager/src/constructor_manager/run.py b/constructor-manager-api/src/constructor_manager_api/run.py similarity index 91% rename from constructor-manager/src/constructor_manager/run.py rename to constructor-manager-api/src/constructor_manager_api/run.py index 60605b05..0d874ab9 100644 --- a/constructor-manager/src/constructor_manager/run.py +++ b/constructor-manager-api/src/constructor_manager_api/run.py @@ -4,7 +4,7 @@ from qtpy.QtCore import QCoreApplication, QTimer # type: ignore -from constructor_manager.api import check_updates, check_version, open_manager +from constructor_manager_api.api import check_updates, check_version, open_manager def _finished(res): 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/src/constructor_manager/utils/worker.py b/constructor-manager-api/src/constructor_manager_api/utils/worker.py similarity index 80% rename from constructor-manager/src/constructor_manager/utils/worker.py rename to constructor-manager-api/src/constructor_manager_api/utils/worker.py index 5e26585e..0ce732a8 100644 --- a/constructor-manager/src/constructor_manager/utils/worker.py +++ b/constructor-manager-api/src/constructor_manager_api/utils/worker.py @@ -2,10 +2,11 @@ import json import logging +from typing import List from qtpy.QtCore import QObject, QProcess, Signal # type: ignore -from constructor_manager.utils.conda import get_base_prefix +from constructor_manager_api.utils.conda import get_prefix_by_name logger = logging.getLogger(__name__) @@ -22,14 +23,14 @@ class ConstructorManagerWorker(QObject): Run the process detached, by default ``False``. """ - _WORKERS: 'ConstructorManagerWorker' = [] + _WORKERS: 'List[ConstructorManagerWorker]' = [] finished = Signal(dict) def __init__(self, args, detached=False): super().__init__() ConstructorManagerWorker._WORKERS.append(self) self._detached = detached - self._program = get_base_prefix() / "bin" / "constructor-manager" + self._program = self._executable() if not self._program.is_file(): raise FileNotFoundError(f"Could not find {self._program}") @@ -41,6 +42,18 @@ def __init__(self, args, detached=False): 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) diff --git a/constructor-manager/tox.ini b/constructor-manager-api/tox.ini similarity index 100% rename from constructor-manager/tox.ini rename to constructor-manager-api/tox.ini diff --git a/constructor-manager/src/constructor_manager/utils/__init__.py b/constructor-manager/src/constructor_manager/utils/__init__.py deleted file mode 100644 index e9bca1b2..00000000 --- a/constructor-manager/src/constructor_manager/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -VERSION_INFO = (0, 1, 0) -__version__ = "0.1.0" diff --git a/constructor-manager/src/constructor_manager/utils/_tests/test_worker.py b/constructor-manager/src/constructor_manager/utils/_tests/test_worker.py deleted file mode 100644 index 94027677..00000000 --- a/constructor-manager/src/constructor_manager/utils/_tests/test_worker.py +++ /dev/null @@ -1,5 +0,0 @@ -from constructor_manager.utils.conda import get_base_prefix - - -def test_worker(): - assert get_base_prefix() diff --git a/constructor-manager/src/constructor_manager/utils/conda.py b/constructor-manager/src/constructor_manager/utils/conda.py deleted file mode 100644 index b11006bf..00000000 --- a/constructor-manager/src/constructor_manager/utils/conda.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Conda utilities.""" - -import sys -from pathlib import Path - - -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 From aca94512a259945b0f843b1d640221163906e1af Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 06:54:36 -0500 Subject: [PATCH 05/17] Add settings --- .../src/constructor_manager_api/api.py | 81 ++++++++++++------- .../src/constructor_manager_api/run.py | 12 ++- .../src/constructor_manager_api/utils/io.py | 18 +++++ .../constructor_manager_api/utils/settings.py | 33 ++++++++ 4 files changed, 110 insertions(+), 34 deletions(-) create mode 100644 constructor-manager-api/src/constructor_manager_api/utils/io.py create mode 100644 constructor-manager-api/src/constructor_manager_api/utils/settings.py diff --git a/constructor-manager-api/src/constructor_manager_api/api.py b/constructor-manager-api/src/constructor_manager_api/api.py index 45614e02..43d54b68 100644 --- a/constructor-manager-api/src/constructor_manager_api/api.py +++ b/constructor-manager-api/src/constructor_manager_api/api.py @@ -5,7 +5,8 @@ from constructor_manager_api.defaults import DEFAULT_CHANNEL from constructor_manager_api.utils.worker import ConstructorManagerWorker -from constructor_manager_api.utils.conda import get_base_prefix +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 @@ -21,6 +22,8 @@ def _run_action( channels: Optional[List[str]] = None, dev: bool = False, plugins_url: Optional[str] = None, + target_prefix: Optional[str] = None, + delayed: bool = False, ) -> ConstructorManagerWorker: """Run constructor action. @@ -39,6 +42,10 @@ def _run_action( 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``. Returns ------- @@ -68,6 +75,13 @@ def _run_action( if dev: args.extend(["--dev"]) + if target_prefix: + args.append("--target-prefix") + args.append(target_prefix) + + if delayed: + args.append("--delayed") + detached = cmd != "status" detached = False @@ -152,8 +166,12 @@ def check_packages(package_name: str, version: Optional[str] = None, plugins_url def update( package_name, + 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. @@ -164,8 +182,9 @@ def update( Worker to check for updates. Includes a finished signal that returns a ``dict`` with the result. """ + return _run_action( - "update", package_name, channels=channels, dev=dev + "update", package_name, version, build_string=build_string, channels=channels, dev=dev, delayed=delayed, plugins_url=plugins_url ) @@ -230,7 +249,7 @@ def revert( a ``dict`` with the result. """ return _run_action( - "rollback", + "revert", package_name, version=current_version, channels=channels, @@ -285,7 +304,8 @@ def open_manager( build_string: Optional[str] = None, channels: Optional[List[str]] = None, dev: bool = False, - ) -> QProcess: + log: Optional[str] = None, + ) -> bool: """ Open the constructor manager. @@ -303,51 +323,50 @@ def open_manager( dev : bool, optional Check for development version, by default ``False``. """ - path = ( - get_base_prefix() - / "bin" - / "constructor-manager-ui" - ) + 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: - args.extend(['--current-version', current_version]) + settings['current_version'] = current_version if plugins_url: - args.extend(['--plugins-url', plugins_url]) + settings['plugins_url'] = plugins_url if build_string: - args.extend(['--build-string', build_string]) + settings['build_string'] = build_string if dev: - args.extend(['--dev']) + settings['dev'] = dev # type: ignore if channels: - for channel in channels: - args.extend(['--channel', channel]) + settings['channels'] = channels # type: ignore - process = QProcess() - process.start( + if log: + settings['log'] = log # type: ignore + + save_settings(package_name, settings) + # TODO: use open_application + return QProcess.startDetached( str(path), - args + args, ) - return process -def open_application(package_name, version): +def open_application(package_name, version, target_prefix=None): return _run_action( "open", package_name, version=version, + target_prefix=target_prefix, ) - - -def check_constructor_manager_updates(): - # TODO: Use a separate worker and call conda/mamba directly! - # this could use the same worker and run through constructor-manager-cli - pass - - -def update_constructor_manager(): - # Use a separate worker and call conda/mamba directly! - pass diff --git a/constructor-manager-api/src/constructor_manager_api/run.py b/constructor-manager-api/src/constructor_manager_api/run.py index 0d874ab9..b674bde8 100644 --- a/constructor-manager-api/src/constructor_manager_api/run.py +++ b/constructor-manager-api/src/constructor_manager_api/run.py @@ -30,6 +30,12 @@ def _finished(res): # worker.finished.connect(_finished) # worker.start() - process = open_manager("napari", build_string="pyside", plugins_url="https://api.napari-hub.org/plugins") - process.finished.connect(lambda: sys.exit(0)) - sys.exit(app.exec_()) + 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-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..414c5481 --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/utils/io.py @@ -0,0 +1,18 @@ + +from pathlib import Path + +from constructor_manager_api.utils.conda import get_prefix_by_name + + +def get_config_path() -> Path: + # path = get_prefix_by_name("base") / "var" / "constructor-manager" + 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) -> Path: + # path = get_prefix_by_name("base") / "var" / "constructor-manager" + 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..fbabffa8 --- /dev/null +++ b/constructor-manager-api/src/constructor_manager_api/utils/settings.py @@ -0,0 +1,33 @@ +"""Constuctor manager.""" + +import yaml + +from constructor_manager_api.utils.io import get_settings_path + +settings = { # type: ignore + 'current_version': None, + 'build_string': None, + 'plugins_url': None, + 'channels': [], + 'log': None, + 'dev': None, +} + + +def save_settings(package_name, settings): + """"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): + """"Load constructor manager settings from file per `package_name`.""""" + path = get_settings_path(package_name) + loaded_settings = settings.copy() + if path.exists(): + with open(path, "r") as f: + data = yaml.load(f, Loader=yaml.FullLoader) + loaded_settings.update(data) + + return loaded_settings From d8b7935eae0619f1492b520b1f9b8e28efb94831 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 07:45:07 -0500 Subject: [PATCH 06/17] Update docstrings and types --- .../src/constructor_manager_api/api.py | 140 ++++++++++++++---- .../src/constructor_manager_api/utils/io.py | 6 +- .../constructor_manager_api/utils/settings.py | 11 +- .../constructor_manager_api/utils/worker.py | 9 +- constructor-manager-api/tox.ini | 3 +- 5 files changed, 131 insertions(+), 38 deletions(-) diff --git a/constructor-manager-api/src/constructor_manager_api/api.py b/constructor-manager-api/src/constructor_manager_api/api.py index 43d54b68..2495b916 100644 --- a/constructor-manager-api/src/constructor_manager_api/api.py +++ b/constructor-manager-api/src/constructor_manager_api/api.py @@ -15,7 +15,7 @@ def _run_action( - cmd, + cmd: str, package_name: Optional[str] = None, version: Optional[str] = None, build_string: Optional[str] = None, @@ -24,6 +24,7 @@ def _run_action( plugins_url: Optional[str] = None, target_prefix: Optional[str] = None, delayed: bool = False, + state: Optional[str] = None, ) -> ConstructorManagerWorker: """Run constructor action. @@ -46,6 +47,8 @@ def _run_action( 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 ------- @@ -82,6 +85,11 @@ def _run_action( if delayed: args.append("--delayed") + if state: + args.append("--state") + args.append(state) + + detached = cmd != "status" detached = False @@ -130,7 +138,7 @@ def check_updates( ) -def check_version(package_name: str,) -> ConstructorManagerWorker: +def check_version(package_name: str) -> ConstructorManagerWorker: """Check for updates. Parameters @@ -147,13 +155,19 @@ def check_version(package_name: str,) -> ConstructorManagerWorker: return _run_action("check-version", package_name) -def check_packages(package_name: str, version: Optional[str] = None, plugins_url: Optional[str] = None) -> ConstructorManagerWorker: +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``. Returns ------- @@ -165,8 +179,8 @@ def check_packages(package_name: str, version: Optional[str] = None, plugins_url def update( - package_name, - version: str, + package_name: str, + current_version: str, build_string: Optional[str] = None, channels: Optional[List[str]] = None, plugins_url: Optional[str] = None, @@ -174,34 +188,62 @@ def update( 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( - "update", package_name, version, build_string=build_string, channels=channels, dev=dev, delayed=delayed, plugins_url=plugins_url + "update", + package_name, + current_version, + build_string=build_string, + channels=channels, + dev=dev, + delayed=delayed, + plugins_url=plugins_url, ) def restore( - package_name, - version, + package_name: str, + current_version: str, + state: str, channels: Optional[List[str]] = None, dev: bool = False, ) -> ConstructorManagerWorker: - """Restore the current version of package. + """Restore to a given saved state within the current version. Parameters --------- package_name : str Name of the package to check for updates. - version : str, optional + 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 @@ -216,28 +258,28 @@ def restore( return _run_action( "restore", package_name, - version=version, + version=current_version, channels=channels, dev=dev, + state=state, ) def revert( - package_name, + package_name: str, current_version: Optional[str], channels: Optional[List[str]] = None, dev: bool = False, ) -> ConstructorManagerWorker: - """Update the package to given version. - If version is None update to latest version found. + """Revert to a previous version state of the current 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 + 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``. @@ -258,13 +300,15 @@ def revert( def reset( - package_name, + package_name: str, current_version: Optional[str], channels: Optional[List[str]] = None, dev: bool = False, ) -> ConstructorManagerWorker: - """Update the package to given version. - If version is None update to latest version found. + """Reset an environment from scratch. + + This will remove all packages and reinstall the current version + of the package. Parameters --------- @@ -292,13 +336,42 @@ def reset( ) -def get_status(): - """Get status for the state of the constructor updater.""" - return _run_action("status") +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( + "lock-environment", + package_name, + version=current_version, + plugins_url=plugins_url, + ) def open_manager( - package_name, + package_name: str, current_version: Optional[str] = None, plugins_url: Optional[str] = None, build_string: Optional[str] = None, @@ -356,14 +429,27 @@ def open_manager( settings['log'] = log # type: ignore save_settings(package_name, settings) - # TODO: use open_application + # 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, version, target_prefix=None): +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( "open", package_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 index 414c5481..2ffadb8c 100644 --- a/constructor-manager-api/src/constructor_manager_api/utils/io.py +++ b/constructor-manager-api/src/constructor_manager_api/utils/io.py @@ -5,14 +5,14 @@ def get_config_path() -> Path: - # path = get_prefix_by_name("base") / "var" / "constructor-manager" + """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) -> Path: - # path = get_prefix_by_name("base") / "var" / "constructor-manager" +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 index fbabffa8..63079e40 100644 --- a/constructor-manager-api/src/constructor_manager_api/utils/settings.py +++ b/constructor-manager-api/src/constructor_manager_api/utils/settings.py @@ -1,10 +1,13 @@ """Constuctor manager.""" +from typing import Any, Dict + import yaml from constructor_manager_api.utils.io import get_settings_path -settings = { # type: ignore + +_default_settings = { # type: ignore 'current_version': None, 'build_string': None, 'plugins_url': None, @@ -14,17 +17,17 @@ } -def save_settings(package_name, settings): +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): +def load_settings(package_name: str): """"Load constructor manager settings from file per `package_name`.""""" path = get_settings_path(package_name) - loaded_settings = settings.copy() + loaded_settings = _default_settings.copy() if path.exists(): with open(path, "r") as f: data = yaml.load(f, Loader=yaml.FullLoader) diff --git a/constructor-manager-api/src/constructor_manager_api/utils/worker.py b/constructor-manager-api/src/constructor_manager_api/utils/worker.py index 0ce732a8..3a995bea 100644 --- a/constructor-manager-api/src/constructor_manager_api/utils/worker.py +++ b/constructor-manager-api/src/constructor_manager_api/utils/worker.py @@ -35,7 +35,7 @@ def __init__(self, args, detached=False): if not self._program.is_file(): raise FileNotFoundError(f"Could not find {self._program}") - # TODO: Environemnt variables? + # TODO: check environemnt variables self._args = args self._process = QProcess() self._process.setArguments(args) @@ -56,7 +56,11 @@ def _executable(): 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) + 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() @@ -65,7 +69,6 @@ def _finished(self, exit_code: int, exit_status: QProcess.ExitStatus = QProcess. self.finished.emit({"data": {}, "error": str(e)}) return - # TODO: Ensure to have the proper propagation of issues raw_output = stdout.data().decode() raw_error = stderr.data().decode() error = raw_error diff --git a/constructor-manager-api/tox.ini b/constructor-manager-api/tox.ini index b1f09aa6..f73994ab 100644 --- a/constructor-manager-api/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 = From 76c66a732e321178a83e0f203a379f94dd79563f Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 07:49:13 -0500 Subject: [PATCH 07/17] Fix code style --- .../src/constructor_manager_api/__init__.py | 2 -- .../src/constructor_manager_api/api.py | 31 ++++++++--------- .../src/constructor_manager_api/run.py | 12 ++++--- .../src/constructor_manager_api/utils/io.py | 3 +- .../constructor_manager_api/utils/settings.py | 18 +++++----- .../constructor_manager_api/utils/worker.py | 34 ++++++++++++------- 6 files changed, 55 insertions(+), 45 deletions(-) diff --git a/constructor-manager-api/src/constructor_manager_api/__init__.py b/constructor-manager-api/src/constructor_manager_api/__init__.py index db7f0992..e9bca1b2 100644 --- a/constructor-manager-api/src/constructor_manager_api/__init__.py +++ b/constructor-manager-api/src/constructor_manager_api/__init__.py @@ -1,4 +1,2 @@ -from constructor_manager_api.api import * - 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 index 2495b916..775a9a65 100644 --- a/constructor-manager-api/src/constructor_manager_api/api.py +++ b/constructor-manager-api/src/constructor_manager_api/api.py @@ -89,7 +89,6 @@ def _run_action( args.append("--state") args.append(state) - detached = cmd != "status" detached = False @@ -103,7 +102,9 @@ def check_updates( package_name: str, current_version: Optional[str] = None, build_string: Optional[str] = None, - channels: List[str] = [DEFAULT_CHANNEL, ], + channels: List[str] = [ + DEFAULT_CHANNEL, + ], dev: bool = False, ) -> ConstructorManagerWorker: """Check for updates. @@ -175,7 +176,9 @@ def check_packages( Worker to check for updates. Includes a finished signal that returns a ``dict`` with the result. """ - return _run_action("check-packages", package_name, version=version, plugins_url=plugins_url) + return _run_action( + "check-packages", package_name, version=version, plugins_url=plugins_url + ) def update( @@ -378,7 +381,7 @@ def open_manager( channels: Optional[List[str]] = None, dev: bool = False, log: Optional[str] = None, - ) -> bool: +) -> bool: """ Open the constructor manager. @@ -396,14 +399,10 @@ def open_manager( dev : bool, optional Check for development version, by default ``False``. """ - envs = ['_constructor-manager', 'constructor-manager', 'base'] + envs = ["_constructor-manager", "constructor-manager", "base"] for env in envs: target_prefix = get_prefix_by_name(env) - path = ( - target_prefix - / "bin" - / "constructor-manager-ui" - ) + path = target_prefix / "bin" / "constructor-manager-ui" if path.exists(): break @@ -411,22 +410,22 @@ def open_manager( args = [package_name] if current_version: - settings['current_version'] = current_version + settings["current_version"] = current_version if plugins_url: - settings['plugins_url'] = plugins_url + settings["plugins_url"] = plugins_url if build_string: - settings['build_string'] = build_string + settings["build_string"] = build_string if dev: - settings['dev'] = dev # type: ignore + settings["dev"] = dev # type: ignore if channels: - settings['channels'] = channels # type: ignore + settings["channels"] = channels # type: ignore if log: - settings['log'] = log # type: ignore + settings["log"] = log # type: ignore save_settings(package_name, settings) # TODO: For a separate PR, use open_application when the convention diff --git a/constructor-manager-api/src/constructor_manager_api/run.py b/constructor-manager-api/src/constructor_manager_api/run.py index b674bde8..a6b7853d 100644 --- a/constructor-manager-api/src/constructor_manager_api/run.py +++ b/constructor-manager-api/src/constructor_manager_api/run.py @@ -4,7 +4,7 @@ from qtpy.QtCore import QCoreApplication, QTimer # type: ignore -from constructor_manager_api.api import check_updates, check_version, open_manager +from constructor_manager_api.api import open_manager def _finished(res): @@ -25,7 +25,11 @@ def _finished(res): # channel="napari", # dev=True, # ) - # worker = check_updates("napari", build_string="pyside", plugins_url="https://api.napari-hub.org/plugins") + # 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() @@ -34,8 +38,8 @@ def _finished(res): "napari", build_string="pyside", plugins_url="https://api.napari-hub.org/plugins", - channels=['napari', 'conda-forge'], - log='DEBUG', + channels=["napari", "conda-forge"], + log="DEBUG", ) sys.exit(process) # sys.exit(app.exec_()) diff --git a/constructor-manager-api/src/constructor_manager_api/utils/io.py b/constructor-manager-api/src/constructor_manager_api/utils/io.py index 2ffadb8c..ee1fb7df 100644 --- a/constructor-manager-api/src/constructor_manager_api/utils/io.py +++ b/constructor-manager-api/src/constructor_manager_api/utils/io.py @@ -1,4 +1,3 @@ - from pathlib import Path from constructor_manager_api.utils.conda import get_prefix_by_name @@ -13,6 +12,6 @@ def get_config_path() -> 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 = 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 index 63079e40..da49bdba 100644 --- a/constructor-manager-api/src/constructor_manager_api/utils/settings.py +++ b/constructor-manager-api/src/constructor_manager_api/utils/settings.py @@ -8,28 +8,28 @@ _default_settings = { # type: ignore - 'current_version': None, - 'build_string': None, - 'plugins_url': None, - 'channels': [], - 'log': None, - 'dev': None, + "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`.""""" + """"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`.""""" + """"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, "r") as f: + with open(path) as f: data = yaml.load(f, Loader=yaml.FullLoader) loaded_settings.update(data) diff --git a/constructor-manager-api/src/constructor_manager_api/utils/worker.py b/constructor-manager-api/src/constructor_manager_api/utils/worker.py index 3a995bea..6b09f88f 100644 --- a/constructor-manager-api/src/constructor_manager_api/utils/worker.py +++ b/constructor-manager-api/src/constructor_manager_api/utils/worker.py @@ -23,7 +23,7 @@ class ConstructorManagerWorker(QObject): Run the process detached, by default ``False``. """ - _WORKERS: 'List[ConstructorManagerWorker]' = [] + _WORKERS: "List[ConstructorManagerWorker]" = [] finished = Signal(dict) def __init__(self, args, detached=False): @@ -35,7 +35,6 @@ def __init__(self, args, detached=False): if not self._program.is_file(): raise FileNotFoundError(f"Could not find {self._program}") - # TODO: check environemnt variables self._args = args self._process = QProcess() self._process.setArguments(args) @@ -46,18 +45,25 @@ def __init__(self, args, detached=False): def _executable(): """Get the executable for the constructor manager.""" bin = "constructor-manager-cli" - envs = ['_constructor-manager', 'constructor-manager', 'base'] + 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): + 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), + "Worker with args `%s` finished with exit code %s and exit status %s", + " ".join(self._args), exit_code, exit_status, ) @@ -65,7 +71,6 @@ def _finished(self, exit_code: int, exit_status: QProcess.ExitStatus = QProcess. stdout = self._process.readAllStandardOutput() stderr = self._process.readAllStandardError() except RuntimeError as e: - # TODO: Fix this? self.finished.emit({"data": {}, "error": str(e)}) return @@ -77,12 +82,17 @@ def _finished(self, exit_code: int, exit_status: QProcess.ExitStatus = QProcess. if exit_code == 0 and exit_status == QProcess.ExitStatus.NormalExit: try: output = json.loads(raw_output) - except Exception as e: + 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} + result = { + "data": data, + "error": error, + "exit_code": exit_code, + "exit_status": exit_status, + } self.finished.emit(result) def state(self): @@ -91,10 +101,10 @@ def state(self): def start(self): """Start the worker.""" - logger.debug("Worker with args `%s` started!", ' '.join(self._args)) + 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)) + logger.debug("Worker with args `%s` terminated!", " ".join(self._args)) self._process.terminate() From 3641dd23d5617c41928bec43da462bbb993c8994 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 07:58:33 -0500 Subject: [PATCH 08/17] Use enum for actions --- .../src/constructor_manager_api/api.py | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/constructor-manager-api/src/constructor_manager_api/api.py b/constructor-manager-api/src/constructor_manager_api/api.py index 775a9a65..442495fa 100644 --- a/constructor-manager-api/src/constructor_manager_api/api.py +++ b/constructor-manager-api/src/constructor_manager_api/api.py @@ -1,5 +1,6 @@ """Constructor manager api.""" +from enum import Enum from typing import List, Optional import logging @@ -14,8 +15,20 @@ 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: str, + cmd: ActionsEnum, package_name: Optional[str] = None, version: Optional[str] = None, build_string: Optional[str] = None, @@ -30,7 +43,7 @@ def _run_action( Parameters ---------- - cmd : str + cmd : ActionsEnum Action to run. package_name : str, optional Name of the package to execute action on. @@ -56,7 +69,7 @@ def _run_action( Worker to check for updates. Includes a finished signal that returns a ``dict`` with the result. """ - args = [cmd] + args = [cmd.value] if version is None: version = "*" @@ -130,7 +143,7 @@ def check_updates( a ``dict`` with the result. """ return _run_action( - "check-updates", + ActionsEnum.check_updates, package_name, version=current_version, build_string=build_string, @@ -153,7 +166,7 @@ def check_version(package_name: str) -> ConstructorManagerWorker: Worker to check for updates. Includes a finished signal that returns a ``dict`` with the result. """ - return _run_action("check-version", package_name) + return _run_action(ActionsEnum.check_version, package_name) def check_packages( @@ -177,7 +190,10 @@ def check_packages( a ``dict`` with the result. """ return _run_action( - "check-packages", package_name, version=version, plugins_url=plugins_url + ActionsEnum.check_packages, + package_name, + version=version, + plugins_url=plugins_url, ) @@ -219,7 +235,7 @@ def update( a ``dict`` with the result. """ return _run_action( - "update", + ActionsEnum.update, package_name, current_version, build_string=build_string, @@ -259,7 +275,7 @@ def restore( a ``dict`` with the result. """ return _run_action( - "restore", + ActionsEnum.restore, package_name, version=current_version, channels=channels, @@ -294,7 +310,7 @@ def revert( a ``dict`` with the result. """ return _run_action( - "revert", + ActionsEnum.revert, package_name, version=current_version, channels=channels, @@ -331,7 +347,7 @@ def reset( a ``dict`` with the result. """ return _run_action( - "reset", + ActionsEnum.reset, package_name, version=current_version, channels=channels, @@ -366,7 +382,7 @@ def lock_environment( a ``dict`` with the result. """ return _run_action( - "lock-environment", + ActionsEnum.lock_environment, package_name, version=current_version, plugins_url=plugins_url, @@ -450,7 +466,7 @@ def open_application(package_name: str, version: str, target_prefix=None): Target prefix to open the application in, by default ``None``. """ return _run_action( - "open", + ActionsEnum.open, package_name, version=version, target_prefix=target_prefix, From 073cc3920343b5b9820b28bfb4ea0885f193ea80 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 08:16:03 -0500 Subject: [PATCH 09/17] Update workflows --- .github/workflows/tests.yml | 50 +++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 466536a8..04236c59 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,9 @@ 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,35 @@ 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 - - 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 - - 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 + git checkout https://github.com/goanpeca/packaging/ + cd packaging + cd constructor-manager + pip install -e . --no-deps - - name: Test constructor-manager + - name: Install constructor-manager run: | - cd constructor-manager - python -m tox - env: - PLATFORM: ${{ matrix.platform }} + conda activate constructor-manager + cd constructor-manager-api + pip install -e . --no-deps + + - name: Test constructor-manager-api + run: | + conda activate constructor-manager + conda list + From c0ccc01e228296d2fea175a5ecda9b10ee3df0b6 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 08:18:27 -0500 Subject: [PATCH 10/17] Test --- .github/workflows/tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 04236c59..0ba7b77e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,7 +35,7 @@ jobs: - name: Install dependencies on main environment run: | - conda install -n base conda-lock mamba + conda install -n base conda-lock mamba -y -c conda-forge - name: create constructor-manager-environment run: | @@ -59,4 +59,3 @@ jobs: run: | conda activate constructor-manager conda list - From 903d24267a218af49d52dba72a50d2a7f76c9d57 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 08:20:42 -0500 Subject: [PATCH 11/17] Test --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0ba7b77e..1543c24b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,12 +44,12 @@ jobs: - name: Install constructor-manager run: | conda activate constructor-manager - git checkout https://github.com/goanpeca/packaging/ + git checkout https://github.com/goanpeca/packaging.git cd packaging cd constructor-manager pip install -e . --no-deps - - name: Install constructor-manager + - name: Install constructor-manager-api run: | conda activate constructor-manager cd constructor-manager-api From 9fa4de2378a3952d0387d4a15c2d6d5fb6d1df65 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 08:31:32 -0500 Subject: [PATCH 12/17] Test --- .github/workflows/tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1543c24b..f141e95a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,14 +44,16 @@ jobs: - name: Install constructor-manager run: | conda activate constructor-manager - git checkout https://github.com/goanpeca/packaging.git - cd packaging + 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 - name: Install constructor-manager-api run: | conda activate constructor-manager + conda install -n constructor-manager qtpy -y -c conda-forge cd constructor-manager-api pip install -e . --no-deps From 4fa4c4b1263dcf167d4b1315b975b4d0bd139931 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 08:59:25 -0500 Subject: [PATCH 13/17] test --- .github/workflows/tests.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f141e95a..9791ef43 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,10 +36,13 @@ jobs: - name: Install dependencies on main environment run: | conda install -n base conda-lock mamba -y -c conda-forge + conda list - name: create constructor-manager-environment run: | conda create -n constructor-manager conda packaging requests pyyaml -y -c conda-forge + conda activate constructor-manager + conda list - name: Install constructor-manager run: | @@ -49,11 +52,12 @@ jobs: git checkout constructor-updater cd constructor-manager pip install -e . --no-deps + conda list - name: Install constructor-manager-api run: | conda activate constructor-manager - conda install -n constructor-manager qtpy -y -c conda-forge + conda install -n constructor-manager qtpy pyqt -y -c conda-forge cd constructor-manager-api pip install -e . --no-deps From 2a4cbc6f946a4173a96d6667b323792cd9c4acb3 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 10:20:18 -0500 Subject: [PATCH 14/17] test --- .github/workflows/tests.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9791ef43..fd93cc4c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,7 +9,7 @@ on: - main paths: - 'constructor-manager/**' - - 'constructor-manager-cli/**' + - 'constructor-manager-api/**' workflow_dispatch: jobs: @@ -42,16 +42,23 @@ jobs: run: | conda create -n constructor-manager conda packaging requests pyyaml -y -c conda-forge conda activate constructor-manager + # List installed packages conda list - name: Install constructor-manager run: | conda activate constructor-manager + # Install dev branch + pip install https://github.com/user/repo.git@branch + # 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 pytes-cov pytest-qt -c conda-forge -y + # List installed packages conda list - name: Install constructor-manager-api @@ -64,4 +71,8 @@ jobs: - 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 From 5193ed6200a12a83f334b36da30e2f13066545d2 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 10:35:23 -0500 Subject: [PATCH 15/17] test --- .github/workflows/tests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fd93cc4c..8468d4fe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,8 +48,6 @@ jobs: - name: Install constructor-manager run: | conda activate constructor-manager - # Install dev branch - pip install https://github.com/user/repo.git@branch # Install constructor manager git clone https://github.com/goanpeca/packaging.git packaging_clone cd packaging_clone @@ -57,7 +55,7 @@ jobs: cd constructor-manager pip install -e . --no-deps # Install test deps - conda install -n constructor-manager pytest pytes-cov pytest-qt -c conda-forge -y + conda install -n constructor-manager pytest pytes-cov pytest-qt -c conda-forge -y # List installed packages conda list From b7e970eac4a7ac75cb6e29b54cc7822a69f94803 Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Mon, 13 Mar 2023 10:38:56 -0500 Subject: [PATCH 16/17] test --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8468d4fe..1bb2d41a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,7 +55,7 @@ jobs: cd constructor-manager pip install -e . --no-deps # Install test deps - conda install -n constructor-manager pytest pytes-cov pytest-qt -c conda-forge -y + conda install -n constructor-manager pytest pytest-cov pytest-qt -c conda-forge -y # List installed packages conda list From dffe0cb1b6d02400b91113beb9bd7a3369e6b13a Mon Sep 17 00:00:00 2001 From: Gonzalo Pena-Castellanos Date: Wed, 31 May 2023 08:39:11 -0500 Subject: [PATCH 17/17] Add code review comments --- constructor-manager-api/README.md | 43 ++++++++++++++++++- constructor-manager-api/setup.cfg | 1 + .../src/constructor_manager_api/api.py | 9 ++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/constructor-manager-api/README.md b/constructor-manager-api/README.md index 8b97eef5..3586b69c 100644 --- a/constructor-manager-api/README.md +++ b/constructor-manager-api/README.md @@ -8,18 +8,57 @@ ## Usage ```python -from constructor_manager.api import check_updates +from constructor_manager_api.api import check_updates def finished(result): print(result) -worker = check_updates(package_name="napari", current_version="0.4.10", channel="conda-forge") +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-api/setup.cfg b/constructor-manager-api/setup.cfg index afc02d79..13818da1 100644 --- a/constructor-manager-api/setup.cfg +++ b/constructor-manager-api/setup.cfg @@ -28,6 +28,7 @@ project_urls = [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/api.py b/constructor-manager-api/src/constructor_manager_api/api.py index 442495fa..52fecfe7 100644 --- a/constructor-manager-api/src/constructor_manager_api/api.py +++ b/constructor-manager-api/src/constructor_manager_api/api.py @@ -153,7 +153,7 @@ def check_updates( def check_version(package_name: str) -> ConstructorManagerWorker: - """Check for updates. + """Check for version. Parameters ---------- @@ -163,8 +163,7 @@ def check_version(package_name: str) -> ConstructorManagerWorker: Returns ------- ConstructorManagerWorker - Worker to check for updates. Includes a finished signal that returns - a ``dict`` with the result. + Worker to check for the current version insytalled. """ return _run_action(ActionsEnum.check_version, package_name) @@ -182,6 +181,10 @@ def check_packages( 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 -------