From ff6598b7c61b56246c90789e4f783e71e879d4a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Sun, 16 Jun 2024 22:47:02 -0500 Subject: [PATCH 01/24] Add utility function to query the plugin status from napari side and prevent closing if busy --- .../_tests/test_qt_plugin_dialog.py | 26 +++++++++++++++- napari_plugin_manager/qt_plugin_dialog.py | 30 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/napari_plugin_manager/_tests/test_qt_plugin_dialog.py b/napari_plugin_manager/_tests/test_qt_plugin_dialog.py index 2c188305..5d5e7574 100644 --- a/napari_plugin_manager/_tests/test_qt_plugin_dialog.py +++ b/napari_plugin_manager/_tests/test_qt_plugin_dialog.py @@ -25,6 +25,7 @@ from napari_plugin_manager import qt_plugin_dialog from napari_plugin_manager.qt_package_installer import InstallerActions +from napari_plugin_manager.qt_plugin_dialog import PluginStatus N_MOCKED_PLUGINS = 2 @@ -198,7 +199,6 @@ def set_blocked(self, plugin, blocked): monkeypatch.setattr( napari.plugins, 'plugin_manager', OldPluginManagerMock() ) - monkeypatch.setattr(importlib.metadata, 'metadata', mock_metadata) monkeypatch.setattr(npe2, 'PluginManager', PluginManagerMock()) @@ -385,6 +385,7 @@ def test_add_items_outdated(plugin_dialog, qtbot): assert widget.update_btn.isVisible() +<<<<<<< HEAD @pytest.mark.skipif( qtpy.API_NAME.lower().startswith('pyside') and sys.version_info[:2] > (3, 10) @@ -538,3 +539,26 @@ def test_shortcut_quit(plugin_dialog, qtbot): ) qtbot.wait(200) assert not plugin_dialog.isVisible() +======= +def test_query_status(plugin_dialog, monkeypatch): + res = plugin_dialog.query_status() + assert res['status'] == PluginStatus.IDLE + assert not res['description'] + + monkeypatch.setattr( + plugin_dialog.installer, + "_queue", + ['mock'], + ) + res = plugin_dialog.query_status() + assert res['status'] == PluginStatus.BUSY + assert res['description'] + + monkeypatch.setattr( + plugin_dialog.installer, + "_queue", + ['mock', 'other-mock'], + ) + assert res['status'] == PluginStatus.BUSY + assert res['description'] +>>>>>>> 04b4f8c (Add utility function to query the plugin status from napari side and prevent closing if busy) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index 80a1eccf..6544b8d0 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -3,6 +3,11 @@ import os import sys import webbrowser +import datetime +import importlib.metadata +import os +import uuid +from enum import Enum, auto from functools import partial from pathlib import Path from typing import Dict, List, Literal, NamedTuple, Optional, Sequence, Tuple @@ -83,6 +88,11 @@ def _show_message(widget): warn_dialog.exec_() +class PluginStatus(Enum): + BUSY = auto() + IDLE = auto() + + class ProjectInfoVersions(NamedTuple): metadata: npe2.PackageMetadata display_name: str @@ -1447,6 +1457,26 @@ def set_prefix(self, prefix): # endregion - Public methods + def query_status(self) -> dict: + """Return the current status of the plugin.""" + if self.installer.hasJobs(): + status = PluginStatus.BUSY + description = trans._n( + 'The plugin manager is currently busy with {n} task.', + 'The plugin manager is currently busy with {n} tasks.', + n=self.installer.currentJobs(), + ) + else: + status = PluginStatus.IDLE + description = '' + + return { + "id": uuid.uuid4(), + "timestamp": datetime.datetime.now().isoformat(), + "status": status, + "description": description, + } + if __name__ == "__main__": from qtpy.QtWidgets import QApplication From 2bc5f7a1e56d02f4214aed938f7d08b9dfa1798a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Sun, 16 Jun 2024 23:51:15 -0500 Subject: [PATCH 02/24] Add import check --- napari_plugin_manager/qt_plugin_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index 6544b8d0..743d365b 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -19,7 +19,7 @@ from napari._qt.qthreading import create_worker from napari._qt.widgets.qt_message_popup import WarnPopup from napari._qt.widgets.qt_tooltip import QtToolTipLabel -from napari.plugins.utils import normalized_name +from napari.plugins.utils import PluginStatus, normalized_name from napari.settings import get_settings from napari.utils.misc import ( parse_version, From 6bf33ae10d058b72e2a6a81b0528bcb12d43550f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Mon, 17 Jun 2024 00:00:36 -0500 Subject: [PATCH 03/24] Add import check --- napari_plugin_manager/qt_plugin_dialog.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index 743d365b..c417fc7a 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -19,7 +19,7 @@ from napari._qt.qthreading import create_worker from napari._qt.widgets.qt_message_popup import WarnPopup from napari._qt.widgets.qt_tooltip import QtToolTipLabel -from napari.plugins.utils import PluginStatus, normalized_name +from napari.plugins.utils import normalized_name from napari.settings import get_settings from napari.utils.misc import ( parse_version, @@ -62,6 +62,17 @@ from napari_plugin_manager.qt_widgets import ClickableLabel from napari_plugin_manager.utils import is_conda_package +try: + from napari.plugins.utils import PluginStatus +except ImportError: + + class PluginStatus(Enum): + BUSY = auto() + IDLE = auto() + + +# TODO: add error icon and handle pip install errors + # Scaling factor for each list widget item when expanding. CONDA = 'Conda' PYPI = 'PyPI' @@ -88,11 +99,6 @@ def _show_message(widget): warn_dialog.exec_() -class PluginStatus(Enum): - BUSY = auto() - IDLE = auto() - - class ProjectInfoVersions(NamedTuple): metadata: npe2.PackageMetadata display_name: str From b1c3297cc7d92869a2d0dba26003a3f0e7b7771a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Sun, 28 Jul 2024 23:10:32 -0500 Subject: [PATCH 04/24] Fix imports and format --- napari_plugin_manager/_tests/test_qt_plugin_dialog.py | 5 ++--- napari_plugin_manager/qt_plugin_dialog.py | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/napari_plugin_manager/_tests/test_qt_plugin_dialog.py b/napari_plugin_manager/_tests/test_qt_plugin_dialog.py index 5d5e7574..308bba58 100644 --- a/napari_plugin_manager/_tests/test_qt_plugin_dialog.py +++ b/napari_plugin_manager/_tests/test_qt_plugin_dialog.py @@ -385,7 +385,6 @@ def test_add_items_outdated(plugin_dialog, qtbot): assert widget.update_btn.isVisible() -<<<<<<< HEAD @pytest.mark.skipif( qtpy.API_NAME.lower().startswith('pyside') and sys.version_info[:2] > (3, 10) @@ -539,7 +538,8 @@ def test_shortcut_quit(plugin_dialog, qtbot): ) qtbot.wait(200) assert not plugin_dialog.isVisible() -======= + + def test_query_status(plugin_dialog, monkeypatch): res = plugin_dialog.query_status() assert res['status'] == PluginStatus.IDLE @@ -561,4 +561,3 @@ def test_query_status(plugin_dialog, monkeypatch): ) assert res['status'] == PluginStatus.BUSY assert res['description'] ->>>>>>> 04b4f8c (Add utility function to query the plugin status from napari side and prevent closing if busy) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index c417fc7a..7d3fb666 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -1,12 +1,10 @@ import contextlib -import importlib.metadata -import os -import sys -import webbrowser import datetime import importlib.metadata import os +import sys import uuid +import webbrowser from enum import Enum, auto from functools import partial from pathlib import Path From 6f30a716202f6bab36a8f6648eb1d797fc852752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Sun, 28 Jul 2024 23:21:31 -0500 Subject: [PATCH 05/24] Add registration and unregistration of processes --- napari_plugin_manager/qt_plugin_dialog.py | 29 ++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index 7d3fb666..9a73bede 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -62,7 +62,13 @@ try: from napari.plugins.utils import PluginStatus + from napari.utils.status import register_process, unregister_process except ImportError: + def register_process(status): + pass + + def unregister_process(status): + pass class PluginStatus(Enum): BUSY = auto() @@ -864,6 +870,7 @@ def __init__(self, parent=None, prefix=None) -> None: self._filter_texts = [] self._filter_idxs_cache = set() self._filter_timer = QTimer(self) + self._latest_status = None self.worker = None # timer to avoid triggering a filter for every keystroke @@ -916,6 +923,21 @@ def _update_theme(self, event): stylesheet = get_current_stylesheet([STYLES_PATH]) self.setStyleSheet(stylesheet) + def _register_process(self): + if self._latest_status is not None: + self._unregister_process(self._latest_status) + + status = self.query_status() + self._latest_status = status + register_process(status) + + def _unregister_process(self): + if isinstance(self._latest_status, dict): + status_id = self._latest_status.get('id', None) + unregister_process(status_id) + + self._latest_status = None + def _on_installer_start(self): """Updates dialog buttons and status when installing a plugin.""" self.cancel_all_btn.setVisible(True) @@ -924,6 +946,9 @@ def _on_installer_start(self): self.process_error_indicator.hide() self.refresh_button.setDisabled(True) + if self._latest_status is None: + self._register_process() + def _on_process_finished(self, process_finished_data: ProcessFinishedData): action = process_finished_data['action'] exit_code = process_finished_data['exit_code'] @@ -977,6 +1002,7 @@ def _on_installer_all_finished(self, exit_codes): self.cancel_all_btn.setVisible(False) self.close_btn.setDisabled(False) self.refresh_button.setDisabled(False) + self._unregister_process() if not self.isVisible(): if sum(exit_codes) > 0: @@ -1474,12 +1500,13 @@ def query_status(self) -> dict: status = PluginStatus.IDLE description = '' - return { + status = { "id": uuid.uuid4(), "timestamp": datetime.datetime.now().isoformat(), "status": status, "description": description, } + return status if __name__ == "__main__": From 7af047b34135426b218e55ea0701ef853c3339e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Sun, 28 Jul 2024 23:21:53 -0500 Subject: [PATCH 06/24] Cleas up status on refresh --- napari_plugin_manager/qt_plugin_dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index 9a73bede..068c9c7a 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -1456,6 +1456,7 @@ def refresh(self, clear_cache: bool = False): self._plugin_queue = [] self._plugin_data = [] self._plugin_data_map = {} + self._latest_status = None self.installed_list.clear() self.available_list.clear() From 3edb8fae053e27171505fa723edf855e5dcb5c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Sun, 28 Jul 2024 23:45:49 -0500 Subject: [PATCH 07/24] Change to stringenum --- napari_plugin_manager/qt_plugin_dialog.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index 068c9c7a..c63fd9f2 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -5,7 +5,7 @@ import sys import uuid import webbrowser -from enum import Enum, auto +from enum import auto from functools import partial from pathlib import Path from typing import Dict, List, Literal, NamedTuple, Optional, Sequence, Tuple @@ -20,6 +20,7 @@ from napari.plugins.utils import normalized_name from napari.settings import get_settings from napari.utils.misc import ( + StringEnum, parse_version, running_as_constructor_app, ) @@ -64,13 +65,14 @@ from napari.plugins.utils import PluginStatus from napari.utils.status import register_process, unregister_process except ImportError: + def register_process(status): pass def unregister_process(status): pass - class PluginStatus(Enum): + class PluginStatus(StringEnum): BUSY = auto() IDLE = auto() From 75156625d17fffda6d2a2e8c832d8037a562b25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Mon, 29 Jul 2024 01:08:38 -0500 Subject: [PATCH 08/24] Rename methods --- .../_tests/test_qt_plugin_dialog.py | 8 ++-- napari_plugin_manager/qt_plugin_dialog.py | 39 ++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/napari_plugin_manager/_tests/test_qt_plugin_dialog.py b/napari_plugin_manager/_tests/test_qt_plugin_dialog.py index 308bba58..505632fc 100644 --- a/napari_plugin_manager/_tests/test_qt_plugin_dialog.py +++ b/napari_plugin_manager/_tests/test_qt_plugin_dialog.py @@ -25,7 +25,7 @@ from napari_plugin_manager import qt_plugin_dialog from napari_plugin_manager.qt_package_installer import InstallerActions -from napari_plugin_manager.qt_plugin_dialog import PluginStatus +from napari_plugin_manager.qt_plugin_dialog import ProcessStatus N_MOCKED_PLUGINS = 2 @@ -542,7 +542,7 @@ def test_shortcut_quit(plugin_dialog, qtbot): def test_query_status(plugin_dialog, monkeypatch): res = plugin_dialog.query_status() - assert res['status'] == PluginStatus.IDLE + assert res['status'] == ProcessStatus.IDLE assert not res['description'] monkeypatch.setattr( @@ -551,7 +551,7 @@ def test_query_status(plugin_dialog, monkeypatch): ['mock'], ) res = plugin_dialog.query_status() - assert res['status'] == PluginStatus.BUSY + assert res['status'] == ProcessStatus.BUSY assert res['description'] monkeypatch.setattr( @@ -559,5 +559,5 @@ def test_query_status(plugin_dialog, monkeypatch): "_queue", ['mock', 'other-mock'], ) - assert res['status'] == PluginStatus.BUSY + assert res['status'] == ProcessStatus.BUSY assert res['description'] diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index c63fd9f2..9dfa1361 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -62,17 +62,20 @@ from napari_plugin_manager.utils import is_conda_package try: - from napari.plugins.utils import PluginStatus - from napari.utils.status import register_process, unregister_process + from napari.utils.status import ( + ProcessStatus, + register_process_status, + unregister_process_status, + ) except ImportError: - def register_process(status): + def register_process_status(status): pass - def unregister_process(status): + def unregister_process_status(status): pass - class PluginStatus(StringEnum): + class ProcessStatus(StringEnum): BUSY = auto() IDLE = auto() @@ -872,7 +875,7 @@ def __init__(self, parent=None, prefix=None) -> None: self._filter_texts = [] self._filter_idxs_cache = set() self._filter_timer = QTimer(self) - self._latest_status = None + self._latest_process_status = None self.worker = None # timer to avoid triggering a filter for every keystroke @@ -925,20 +928,20 @@ def _update_theme(self, event): stylesheet = get_current_stylesheet([STYLES_PATH]) self.setStyleSheet(stylesheet) - def _register_process(self): - if self._latest_status is not None: - self._unregister_process(self._latest_status) + def _register_process_status(self): + if self._latest_process_status is not None: + self._unregister_process_status(self._latest_process_status) status = self.query_status() - self._latest_status = status - register_process(status) + self._latest_process_status = status + register_process_status(status) - def _unregister_process(self): - if isinstance(self._latest_status, dict): - status_id = self._latest_status.get('id', None) - unregister_process(status_id) + def _unregister_process_status(self): + if isinstance(self._latest_process_status, dict): + status_id = self._latest_process_status.get('id', None) + unregister_process_status(status_id) - self._latest_status = None + self._latest_process_status = None def _on_installer_start(self): """Updates dialog buttons and status when installing a plugin.""" @@ -1493,14 +1496,14 @@ def set_prefix(self, prefix): def query_status(self) -> dict: """Return the current status of the plugin.""" if self.installer.hasJobs(): - status = PluginStatus.BUSY + status = ProcessStatus.BUSY description = trans._n( 'The plugin manager is currently busy with {n} task.', 'The plugin manager is currently busy with {n} tasks.', n=self.installer.currentJobs(), ) else: - status = PluginStatus.IDLE + status = ProcessStatus.IDLE description = '' status = { From de0e9f55edad89ad3afa4b2a18242bcd3ba9690c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Mon, 29 Jul 2024 01:57:47 -0500 Subject: [PATCH 09/24] Use updated api --- .../_tests/test_qt_plugin_dialog.py | 16 +++--- napari_plugin_manager/qt_plugin_dialog.py | 52 ++++++++----------- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/napari_plugin_manager/_tests/test_qt_plugin_dialog.py b/napari_plugin_manager/_tests/test_qt_plugin_dialog.py index 505632fc..a74f107b 100644 --- a/napari_plugin_manager/_tests/test_qt_plugin_dialog.py +++ b/napari_plugin_manager/_tests/test_qt_plugin_dialog.py @@ -541,23 +541,23 @@ def test_shortcut_quit(plugin_dialog, qtbot): def test_query_status(plugin_dialog, monkeypatch): - res = plugin_dialog.query_status() - assert res['status'] == ProcessStatus.IDLE - assert not res['description'] + status, description = plugin_dialog.query_status() + assert status == ProcessStatus.IDLE + assert not description monkeypatch.setattr( plugin_dialog.installer, "_queue", ['mock'], ) - res = plugin_dialog.query_status() - assert res['status'] == ProcessStatus.BUSY - assert res['description'] + status, description = plugin_dialog.query_status() + assert status == ProcessStatus.BUSY + assert description monkeypatch.setattr( plugin_dialog.installer, "_queue", ['mock', 'other-mock'], ) - assert res['status'] == ProcessStatus.BUSY - assert res['description'] + assert status == ProcessStatus.BUSY + assert description diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index 9dfa1361..0559f82b 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -1,5 +1,4 @@ import contextlib -import datetime import importlib.metadata import os import sys @@ -62,25 +61,23 @@ from napari_plugin_manager.utils import is_conda_package try: - from napari.utils.status import ( + from napari.utils.processes import ( ProcessStatus, register_process_status, unregister_process_status, ) except ImportError: - def register_process_status(status): - pass - - def unregister_process_status(status): - pass - class ProcessStatus(StringEnum): BUSY = auto() IDLE = auto() + def register_process_status(status: ProcessStatus, description: str): + pass + + def unregister_process_status(process_status_id: uuid.UUID) -> bool: + pass -# TODO: add error icon and handle pip install errors # Scaling factor for each list widget item when expanding. CONDA = 'Conda' @@ -875,7 +872,7 @@ def __init__(self, parent=None, prefix=None) -> None: self._filter_texts = [] self._filter_idxs_cache = set() self._filter_timer = QTimer(self) - self._latest_process_status = None + self._process_status_id = None self.worker = None # timer to avoid triggering a filter for every keystroke @@ -929,19 +926,17 @@ def _update_theme(self, event): self.setStyleSheet(stylesheet) def _register_process_status(self): - if self._latest_process_status is not None: - self._unregister_process_status(self._latest_process_status) + if self._process_status_id is not None: + self._unregister_process_status(self._process_status_id) - status = self.query_status() - self._latest_process_status = status - register_process_status(status) + status, description = self.query_status() + self._process_status_id = register_process_status(status, description) def _unregister_process_status(self): - if isinstance(self._latest_process_status, dict): - status_id = self._latest_process_status.get('id', None) - unregister_process_status(status_id) + if self._process_status_id is not None: + unregister_process_status(self._process_status_id) - self._latest_process_status = None + self._process_status_id = None def _on_installer_start(self): """Updates dialog buttons and status when installing a plugin.""" @@ -951,8 +946,8 @@ def _on_installer_start(self): self.process_error_indicator.hide() self.refresh_button.setDisabled(True) - if self._latest_status is None: - self._register_process() + if self._process_status_id is None: + self._register_process_status() def _on_process_finished(self, process_finished_data: ProcessFinishedData): action = process_finished_data['action'] @@ -1007,7 +1002,6 @@ def _on_installer_all_finished(self, exit_codes): self.cancel_all_btn.setVisible(False) self.close_btn.setDisabled(False) self.refresh_button.setDisabled(False) - self._unregister_process() if not self.isVisible(): if sum(exit_codes) > 0: @@ -1017,6 +1011,8 @@ def _on_installer_all_finished(self, exit_codes): else: show_info(trans._('Plugin Manager: process completed\n')) + self._unregister_process_status() + def _add_to_available(self, pkg_name): self._add_items_timer.stop() self._plugin_queue.insert(0, self._plugin_data_map[pkg_name]) @@ -1496,23 +1492,17 @@ def set_prefix(self, prefix): def query_status(self) -> dict: """Return the current status of the plugin.""" if self.installer.hasJobs(): - status = ProcessStatus.BUSY + process_status = ProcessStatus.BUSY description = trans._n( 'The plugin manager is currently busy with {n} task.', 'The plugin manager is currently busy with {n} tasks.', n=self.installer.currentJobs(), ) else: - status = ProcessStatus.IDLE + process_status = ProcessStatus.IDLE description = '' - status = { - "id": uuid.uuid4(), - "timestamp": datetime.datetime.now().isoformat(), - "status": status, - "description": description, - } - return status + return process_status, description if __name__ == "__main__": From eb4e191449baee6e6d794a24ce877565228e6690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Pe=C3=B1a-Castellanos?= Date: Mon, 29 Jul 2024 02:24:55 -0500 Subject: [PATCH 10/24] Fix typing --- napari_plugin_manager/qt_plugin_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napari_plugin_manager/qt_plugin_dialog.py b/napari_plugin_manager/qt_plugin_dialog.py index 0559f82b..97a3013b 100644 --- a/napari_plugin_manager/qt_plugin_dialog.py +++ b/napari_plugin_manager/qt_plugin_dialog.py @@ -1489,7 +1489,7 @@ def set_prefix(self, prefix): # endregion - Public methods - def query_status(self) -> dict: + def query_status(self) -> Tuple[ProcessStatus, str]: """Return the current status of the plugin.""" if self.installer.hasJobs(): process_status = ProcessStatus.BUSY From 03cbcbee74f953772f4752c6a2ca1f9074365190 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:09:41 -0500 Subject: [PATCH 11/24] Update logic from process to task status --- .../_tests/test_qt_plugin_dialog.py | 7 +- .../base_qt_plugin_dialog.py | 70 ++++++++++++------- src/napari_plugin_manager/qt_plugin_dialog.py | 57 ++++++++++----- 3 files changed, 87 insertions(+), 47 deletions(-) diff --git a/src/napari_plugin_manager/_tests/test_qt_plugin_dialog.py b/src/napari_plugin_manager/_tests/test_qt_plugin_dialog.py index 86e7d494..416dcd8e 100644 --- a/src/napari_plugin_manager/_tests/test_qt_plugin_dialog.py +++ b/src/napari_plugin_manager/_tests/test_qt_plugin_dialog.py @@ -29,7 +29,6 @@ InstallerActions, InstallerTools, ) -from napari_plugin_manager.base_qt_plugin_dialog import ProcessStatus N_MOCKED_PLUGINS = 2 @@ -677,7 +676,7 @@ def test_import_plugins(plugin_dialog, tmp_path, qtbot): def test_query_status(plugin_dialog, monkeypatch): status, description = plugin_dialog.query_status() - assert status == ProcessStatus.IDLE + assert status == qt_plugin_dialog.Status.DONE assert not description monkeypatch.setattr( @@ -686,7 +685,7 @@ def test_query_status(plugin_dialog, monkeypatch): ['mock'], ) status, description = plugin_dialog.query_status() - assert status == ProcessStatus.BUSY + assert status == qt_plugin_dialog.Status.BUSY assert description monkeypatch.setattr( @@ -694,5 +693,5 @@ def test_query_status(plugin_dialog, monkeypatch): '_queue', ['mock', 'other-mock'], ) - assert status == ProcessStatus.BUSY + assert status == qt_plugin_dialog.Status.BUSY assert description diff --git a/src/napari_plugin_manager/base_qt_plugin_dialog.py b/src/napari_plugin_manager/base_qt_plugin_dialog.py index b7587bc5..e21a84c7 100644 --- a/src/napari_plugin_manager/base_qt_plugin_dialog.py +++ b/src/napari_plugin_manager/base_qt_plugin_dialog.py @@ -3,7 +3,7 @@ import os import uuid import webbrowser -from collections.abc import Sequence +from collections.abc import Callable, Sequence from enum import Enum, auto from functools import partial from logging import getLogger @@ -63,9 +63,11 @@ log = getLogger(__name__) -class ProcessStatus(Enum): +class Status(Enum): + PENDING = auto() BUSY = auto() - IDLE = auto() + DONE = auto() + FAILED = auto() class PackageMetadataProtocol(Protocol): @@ -1055,7 +1057,7 @@ def __init__(self, parent=None, prefix=None) -> None: self._plugin_data = [] # Store all plugin data self._filter_texts = [] self._filter_idxs_cache = set() - self._process_status_id = None + self._task_status_id = None self.worker = None self._plugin_data_map = {} self._add_items_timer = QTimer(self) @@ -1138,20 +1140,24 @@ def _update_theme(self, event: Any) -> None: """ raise NotImplementedError - def _register_process_status(self): - if self._process_status_id is not None: - self._unregister_process_status(self._process_status_id) + def _register_task_status(self): + if self._task_status_id is not None: + self._update_task_status(self._task_status_id) status, description = self.query_status() - self._process_status_id = self.register_process_status( - status, description + self._task_status_id = self.register_task_status( + status, description, cancel_callback=self.installer.cancel_all ) - def _unregister_process_status(self): - if self._process_status_id is not None: - self.unregister_process_status(self._process_status_id) + def _update_task_status( + self, status: Status = Status.DONE, description: str = '' + ): + if self._task_status_id is not None: + self.update_task_status( + self._task_status_id, status, description=description + ) - self._process_status_id = None + self._task_status_id = None def _on_installer_start(self): """Updates dialog buttons and status when installing a plugin.""" @@ -1161,8 +1167,8 @@ def _on_installer_start(self): self.process_error_indicator.hide() self.refresh_button.setDisabled(True) - if self._process_status_id is None: - self._register_process_status() + if self._task_status_id is None: + self._register_task_status() def _on_process_finished(self, process_finished_data: ProcessFinishedData): action = process_finished_data['action'] @@ -1236,7 +1242,7 @@ def _on_installer_all_finished(self, exit_codes): self._trans('Plugin Manager: process completed\n') ) - self._unregister_process_status() + self._update_task_status() self.search() def _add_to_installed( @@ -1801,7 +1807,7 @@ def search(self, text: str | None = None, skip=False) -> None: if len(text.strip()) == 0: self.installed_list.filter('') self.available_list.hideAll() - self._plugin_queue = None + self._plugin_queue = [] self._add_items_timer.stop() self._plugins_found = 0 else: @@ -1819,7 +1825,7 @@ def search(self, text: str | None = None, skip=False) -> None: self._plugins_found = len(items) self._add_items_timer.start() else: - self._plugin_queue = None + self._plugin_queue = [] self._add_items_timer.stop() self._plugins_found = 0 @@ -1896,25 +1902,37 @@ def import_plugins(self, fpath: str) -> None: plugins = [p for p in plugins if p] self._install_packages(plugins) - def register_process_status( - self, status: ProcessStatus, description: str + def register_task_status( + self, + status: Status, + description: str, + cancel_callback: Callable | None = None, ) -> uuid.UUID: - """Register a process status for the plugin manager.""" + """Register a task status for the plugin manager.""" raise NotImplementedError - def unregister_process_status(self, process_status_id: uuid.UUID) -> bool: - """Unregister a process status for the plugin manager.""" + def update_task_status( + self, task_status_id: uuid.UUID, status: Status, description: str = '' + ) -> bool: + """Unregister a task status for the plugin manager.""" raise NotImplementedError - def query_status(self) -> tuple[ProcessStatus, str]: + def query_status(self) -> tuple[Status, str]: """ Return the current status of plugins installations. Returns ------- - A tuple containing the current status (`ProcessStatus`) and a description. + A tuple containing the current status (`Status`) and a description. """ - raise NotImplementedError + if self.installer.hasJobs(): + task_status = Status.BUSY + description = self.trans('The plugin manager is currently busy') + else: + task_status = Status.DONE + description = '' + + return task_status, description # endregion - Public methods diff --git a/src/napari_plugin_manager/qt_plugin_dialog.py b/src/napari_plugin_manager/qt_plugin_dialog.py index f00f5723..b7e740bb 100644 --- a/src/napari_plugin_manager/qt_plugin_dialog.py +++ b/src/napari_plugin_manager/qt_plugin_dialog.py @@ -1,5 +1,6 @@ import sys import uuid +from collections.abc import Callable from pathlib import Path import napari.plugins @@ -39,20 +40,27 @@ from napari_plugin_manager.utils import is_conda_package try: - from napari.utils.processes import ( - ProcessStatus, - register_process_status, - unregister_process_status, + from napari.utils.task_status import ( + Status, + register_task_status, + update_task_status, ) except ImportError: - from napari_plugin_manager.base_qt_plugin_dialog import ProcessStatus + from napari_plugin_manager.base_qt_plugin_dialog import Status - def register_process_status( - status: ProcessStatus, description: str + def register_task_status( + provider: str, + task_status: Status, + description: str, + cancel_callback: Callable | None = None, ) -> uuid.UUID: pass - def unregister_process_status(process_status_id: uuid.UUID) -> bool: + def update_task_status( + task_status_id: uuid.UUID, + status: Status, + description: str = '', + ) -> bool: pass @@ -284,27 +292,42 @@ def _show_warning(self, warning): def _trans(self, text, **kwargs): return trans._(text, **kwargs) - def register_process_status( - self, status: ProcessStatus, description: str + def register_task_status( + self, + task_status: Status, + description: str, + cancel_callback: Callable | None = None, ) -> uuid.UUID: - return register_process_status(status, description) + return register_task_status( + 'napari-plugin-manager', + task_status, + description, + cancel_callback=cancel_callback, + ) - def unregister_process_status(self, process_status_id: uuid.UUID) -> bool: - return unregister_process_status(process_status_id) + def update_task_status( + self, + task_status_id: uuid.UUID, + status: Status, + description: str = '', + ) -> bool: + return update_task_status( + task_status_id, status, description=description + ) - def query_status(self) -> tuple[ProcessStatus, str]: + def query_status(self) -> tuple[Status, str]: if self.installer.hasJobs(): - process_status = ProcessStatus.BUSY + task_status = Status.BUSY description = trans._n( 'The plugin manager is currently busy with {n} task.', 'The plugin manager is currently busy with {n} tasks.', n=self.installer.currentJobs(), ) else: - process_status = ProcessStatus.IDLE + task_status = Status.DONE description = '' - return process_status, description + return task_status, description if __name__ == '__main__': From 80e7932706463df07ab5548cb9321c7aaa296447 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:23:59 -0500 Subject: [PATCH 12/24] Clean up register/update status logic --- src/napari_plugin_manager/base_qt_plugin_dialog.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/napari_plugin_manager/base_qt_plugin_dialog.py b/src/napari_plugin_manager/base_qt_plugin_dialog.py index e21a84c7..3c25d679 100644 --- a/src/napari_plugin_manager/base_qt_plugin_dialog.py +++ b/src/napari_plugin_manager/base_qt_plugin_dialog.py @@ -1141,10 +1141,14 @@ def _update_theme(self, event: Any) -> None: raise NotImplementedError def _register_task_status(self): + status, description = self.query_status() + if self._task_status_id is not None: - self._update_task_status(self._task_status_id) + self._update_task_status( + self._task_status_id, status, description=description + ) + return - status, description = self.query_status() self._task_status_id = self.register_task_status( status, description, cancel_callback=self.installer.cancel_all ) @@ -1157,8 +1161,6 @@ def _update_task_status( self._task_status_id, status, description=description ) - self._task_status_id = None - def _on_installer_start(self): """Updates dialog buttons and status when installing a plugin.""" self.cancel_all_btn.setVisible(True) From ee41c9dcefc5f3eb98a910dee9d6b127684d259b Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:41:34 -0500 Subject: [PATCH 13/24] Wait for processes to get ended to prevent QProcess destroyed warning message --- src/napari_plugin_manager/base_qt_package_installer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/napari_plugin_manager/base_qt_package_installer.py b/src/napari_plugin_manager/base_qt_package_installer.py index 96dcc017..34e88ba4 100644 --- a/src/napari_plugin_manager/base_qt_package_installer.py +++ b/src/napari_plugin_manager/base_qt_package_installer.py @@ -478,6 +478,7 @@ def cancel_all(self): process.errorOccurred.disconnect(self._on_error_occurred) self._end_process(process) + process.waitForFinished() self._queue.clear() self._current_process = None From 95420819d061fb5cbf009c8000e523c08a90b337 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:12:41 -0500 Subject: [PATCH 14/24] Docstring clean up and remove base query_status implementation --- src/napari_plugin_manager/base_qt_plugin_dialog.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/napari_plugin_manager/base_qt_plugin_dialog.py b/src/napari_plugin_manager/base_qt_plugin_dialog.py index 3c25d679..8452637d 100644 --- a/src/napari_plugin_manager/base_qt_plugin_dialog.py +++ b/src/napari_plugin_manager/base_qt_plugin_dialog.py @@ -1916,7 +1916,7 @@ def register_task_status( def update_task_status( self, task_status_id: uuid.UUID, status: Status, description: str = '' ) -> bool: - """Unregister a task status for the plugin manager.""" + """Update task status for the plugin manager.""" raise NotImplementedError def query_status(self) -> tuple[Status, str]: @@ -1928,13 +1928,6 @@ def query_status(self) -> tuple[Status, str]: A tuple containing the current status (`Status`) and a description. """ - if self.installer.hasJobs(): - task_status = Status.BUSY - description = self.trans('The plugin manager is currently busy') - else: - task_status = Status.DONE - description = '' - - return task_status, description + raise NotImplementedError # endregion - Public methods From cd662665ddc4dd8740ceedfada872be0f55b0126 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Tue, 19 Aug 2025 11:01:13 -0500 Subject: [PATCH 15/24] Add cancelled status --- src/napari_plugin_manager/base_qt_plugin_dialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/napari_plugin_manager/base_qt_plugin_dialog.py b/src/napari_plugin_manager/base_qt_plugin_dialog.py index 8452637d..eb54542c 100644 --- a/src/napari_plugin_manager/base_qt_plugin_dialog.py +++ b/src/napari_plugin_manager/base_qt_plugin_dialog.py @@ -67,6 +67,7 @@ class Status(Enum): PENDING = auto() BUSY = auto() DONE = auto() + CANCELLED = auto() FAILED = auto() From 0030bc36281ad9a8a592aacc00573550d09f1ae6 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Tue, 19 Aug 2025 11:33:02 -0500 Subject: [PATCH 16/24] Fixes for actions done after initial task id is created --- src/napari_plugin_manager/base_qt_plugin_dialog.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/napari_plugin_manager/base_qt_plugin_dialog.py b/src/napari_plugin_manager/base_qt_plugin_dialog.py index eb54542c..ff628352 100644 --- a/src/napari_plugin_manager/base_qt_plugin_dialog.py +++ b/src/napari_plugin_manager/base_qt_plugin_dialog.py @@ -1145,9 +1145,7 @@ def _register_task_status(self): status, description = self.query_status() if self._task_status_id is not None: - self._update_task_status( - self._task_status_id, status, description=description - ) + self._update_task_status(status, description=description) return self._task_status_id = self.register_task_status( @@ -1169,9 +1167,7 @@ def _on_installer_start(self): self.process_success_indicator.hide() self.process_error_indicator.hide() self.refresh_button.setDisabled(True) - - if self._task_status_id is None: - self._register_task_status() + self._register_task_status() def _on_process_finished(self, process_finished_data: ProcessFinishedData): action = process_finished_data['action'] From 26506fe5db2a86ab59d5a6857273cb2d59ecac4a Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Wed, 20 Aug 2025 11:30:04 -0500 Subject: [PATCH 17/24] Move waitForFinished process call to be done always after process gets killed/terminated Co-authored-by: jaimergp --- src/napari_plugin_manager/base_qt_package_installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/napari_plugin_manager/base_qt_package_installer.py b/src/napari_plugin_manager/base_qt_package_installer.py index 34e88ba4..e3f27563 100644 --- a/src/napari_plugin_manager/base_qt_package_installer.py +++ b/src/napari_plugin_manager/base_qt_package_installer.py @@ -478,7 +478,6 @@ def cancel_all(self): process.errorOccurred.disconnect(self._on_error_occurred) self._end_process(process) - process.waitForFinished() self._queue.clear() self._current_process = None @@ -596,6 +595,7 @@ def _end_process(self, process: QProcess): process.kill() else: process.terminate() + process.waitForFinished() if self._output_widget: self._output_widget.append( From aefa67e2f2911d03645770c5f6b73065626db6e4 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:41:00 -0500 Subject: [PATCH 18/24] Testing --- src/napari_plugin_manager/_tests/test_installer_process.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/napari_plugin_manager/_tests/test_installer_process.py b/src/napari_plugin_manager/_tests/test_installer_process.py index 9a6efa26..ea39e449 100644 --- a/src/napari_plugin_manager/_tests/test_installer_process.py +++ b/src/napari_plugin_manager/_tests/test_installer_process.py @@ -273,12 +273,12 @@ def test_conda_installer(qtbot, caplog, monkeypatch, tmp_conda_env: Path): with qtbot.waitSignal(installer.allFinished, timeout=600_000): job_id_1 = installer.install( tool=InstallerTools.CONDA, - pkgs=['typing-extensions'], + pkgs=['packaging'], prefix=tmp_conda_env, ) job_id_2 = installer.install( tool=InstallerTools.CONDA, - pkgs=['packaging'], + pkgs=['typing-extensions'], prefix=tmp_conda_env, ) assert installer.currentJobs() == 2 From 7d71e3aba2b632710ef2550d8adbde5d2837ea09 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:57:46 -0500 Subject: [PATCH 19/24] Revert test changes. Call waitForFinished only for cancel all operation --- .../_tests/test_installer_process.py | 4 ++-- src/napari_plugin_manager/base_qt_package_installer.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/napari_plugin_manager/_tests/test_installer_process.py b/src/napari_plugin_manager/_tests/test_installer_process.py index ea39e449..9a6efa26 100644 --- a/src/napari_plugin_manager/_tests/test_installer_process.py +++ b/src/napari_plugin_manager/_tests/test_installer_process.py @@ -273,12 +273,12 @@ def test_conda_installer(qtbot, caplog, monkeypatch, tmp_conda_env: Path): with qtbot.waitSignal(installer.allFinished, timeout=600_000): job_id_1 = installer.install( tool=InstallerTools.CONDA, - pkgs=['packaging'], + pkgs=['typing-extensions'], prefix=tmp_conda_env, ) job_id_2 = installer.install( tool=InstallerTools.CONDA, - pkgs=['typing-extensions'], + pkgs=['packaging'], prefix=tmp_conda_env, ) assert installer.currentJobs() == 2 diff --git a/src/napari_plugin_manager/base_qt_package_installer.py b/src/napari_plugin_manager/base_qt_package_installer.py index e3f27563..31d6eea2 100644 --- a/src/napari_plugin_manager/base_qt_package_installer.py +++ b/src/napari_plugin_manager/base_qt_package_installer.py @@ -477,7 +477,7 @@ def cancel_all(self): process.finished.disconnect(self._on_process_finished) process.errorOccurred.disconnect(self._on_error_occurred) - self._end_process(process) + self._end_process(process, wait_for_finished=True) self._queue.clear() self._current_process = None @@ -588,14 +588,16 @@ def _process_queue(self): process.start() self._current_process = process - def _end_process(self, process: QProcess): + def _end_process(self, process: QProcess, wait_for_finished=False): if os.name == 'nt': # TODO: this might be too agressive and won't allow rollbacks! # investigate whether we can also do .terminate() process.kill() else: process.terminate() - process.waitForFinished() + + if wait_for_finished: + process.waitForFinished() if self._output_widget: self._output_widget.append( From 45e3674098e9e76df7f284a78e980eb263a2cbf6 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Tue, 26 Aug 2025 10:55:38 -0500 Subject: [PATCH 20/24] Set task status following processes exit codes --- .../base_qt_plugin_dialog.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/napari_plugin_manager/base_qt_plugin_dialog.py b/src/napari_plugin_manager/base_qt_plugin_dialog.py index ff628352..f84001f9 100644 --- a/src/napari_plugin_manager/base_qt_plugin_dialog.py +++ b/src/napari_plugin_manager/base_qt_plugin_dialog.py @@ -1231,17 +1231,17 @@ def _on_installer_all_finished(self, exit_codes): if not self.isVisible(): if sum(exit_codes) > 0: - self._show_warning( - self._trans( - 'Plugin Manager: process completed with errors\n' - ) + message = self._trans( + 'Plugin Manager: process completed with errors\n' ) + status = Status.FAILED + self._show_warning(message) else: - self._show_info( - self._trans('Plugin Manager: process completed\n') - ) + message = self._trans('Plugin Manager: process completed\n') + status = Status.DONE + self._show_info(message) - self._update_task_status() + self._update_task_status(status=status, description=message) self.search() def _add_to_installed( From 1286c0c17cc8939a0ab400348418d293f2c36c26 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:09:46 -0500 Subject: [PATCH 21/24] Fix widget visibility status/message definition --- .../base_qt_plugin_dialog.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/napari_plugin_manager/base_qt_plugin_dialog.py b/src/napari_plugin_manager/base_qt_plugin_dialog.py index f84001f9..c6644931 100644 --- a/src/napari_plugin_manager/base_qt_plugin_dialog.py +++ b/src/napari_plugin_manager/base_qt_plugin_dialog.py @@ -1229,17 +1229,19 @@ def _on_installer_all_finished(self, exit_codes): self.close_btn.setDisabled(False) self.refresh_button.setDisabled(False) + if sum(exit_codes) > 0: + message = self._trans( + 'Plugin Manager: process completed with errors\n' + ) + status = Status.FAILED + show_message = self._show_warning + else: + message = self._trans('Plugin Manager: process completed\n') + status = Status.DONE + show_message = self._show_info + if not self.isVisible(): - if sum(exit_codes) > 0: - message = self._trans( - 'Plugin Manager: process completed with errors\n' - ) - status = Status.FAILED - self._show_warning(message) - else: - message = self._trans('Plugin Manager: process completed\n') - status = Status.DONE - self._show_info(message) + show_message(message) self._update_task_status(status=status, description=message) self.search() From e16ae29515ea141bc9e3682dfd985973c38c0c2a Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Tue, 26 Aug 2025 11:40:46 -0500 Subject: [PATCH 22/24] Handle NoneType exit code for PySide2 cancel_all operation --- src/napari_plugin_manager/base_qt_package_installer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/napari_plugin_manager/base_qt_package_installer.py b/src/napari_plugin_manager/base_qt_package_installer.py index 31d6eea2..98241493 100644 --- a/src/napari_plugin_manager/base_qt_package_installer.py +++ b/src/napari_plugin_manager/base_qt_package_installer.py @@ -657,6 +657,10 @@ def _on_process_done( ) if item is not None: + if not isinstance(exit_code, int): + exit_code = 0 + if error: + exit_code = 1 self.processFinished.emit( { 'exit_code': exit_code, From 927299e2cfae95c398312f8e94047c168713f77b Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:31:12 -0500 Subject: [PATCH 23/24] Update Status enum values --- src/napari_plugin_manager/_tests/test_qt_plugin_dialog.py | 2 +- src/napari_plugin_manager/base_qt_plugin_dialog.py | 7 ++++--- src/napari_plugin_manager/qt_plugin_dialog.py | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/napari_plugin_manager/_tests/test_qt_plugin_dialog.py b/src/napari_plugin_manager/_tests/test_qt_plugin_dialog.py index 416dcd8e..d1b11ffe 100644 --- a/src/napari_plugin_manager/_tests/test_qt_plugin_dialog.py +++ b/src/napari_plugin_manager/_tests/test_qt_plugin_dialog.py @@ -676,7 +676,7 @@ def test_import_plugins(plugin_dialog, tmp_path, qtbot): def test_query_status(plugin_dialog, monkeypatch): status, description = plugin_dialog.query_status() - assert status == qt_plugin_dialog.Status.DONE + assert status == qt_plugin_dialog.Status.COMPLETED assert not description monkeypatch.setattr( diff --git a/src/napari_plugin_manager/base_qt_plugin_dialog.py b/src/napari_plugin_manager/base_qt_plugin_dialog.py index c6644931..a713e9f0 100644 --- a/src/napari_plugin_manager/base_qt_plugin_dialog.py +++ b/src/napari_plugin_manager/base_qt_plugin_dialog.py @@ -66,9 +66,10 @@ class Status(Enum): PENDING = auto() BUSY = auto() - DONE = auto() + COMPLETED = auto() CANCELLED = auto() FAILED = auto() + STARTED_FAILED = auto() class PackageMetadataProtocol(Protocol): @@ -1153,7 +1154,7 @@ def _register_task_status(self): ) def _update_task_status( - self, status: Status = Status.DONE, description: str = '' + self, status: Status = Status.COMPLETED, description: str = '' ): if self._task_status_id is not None: self.update_task_status( @@ -1237,7 +1238,7 @@ def _on_installer_all_finished(self, exit_codes): show_message = self._show_warning else: message = self._trans('Plugin Manager: process completed\n') - status = Status.DONE + status = Status.COMPLETED show_message = self._show_info if not self.isVisible(): diff --git a/src/napari_plugin_manager/qt_plugin_dialog.py b/src/napari_plugin_manager/qt_plugin_dialog.py index b7e740bb..c61aecbe 100644 --- a/src/napari_plugin_manager/qt_plugin_dialog.py +++ b/src/napari_plugin_manager/qt_plugin_dialog.py @@ -324,7 +324,7 @@ def query_status(self) -> tuple[Status, str]: n=self.installer.currentJobs(), ) else: - task_status = Status.DONE + task_status = Status.COMPLETED description = '' return task_status, description From d2eee7fde4b86e231329d6f19d679741e88695a6 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Tue, 23 Sep 2025 13:49:03 -0500 Subject: [PATCH 24/24] Follow changes over napari PR where the status manager is created per window --- src/napari_plugin_manager/qt_plugin_dialog.py | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/src/napari_plugin_manager/qt_plugin_dialog.py b/src/napari_plugin_manager/qt_plugin_dialog.py index a9f841ce..12899b62 100644 --- a/src/napari_plugin_manager/qt_plugin_dialog.py +++ b/src/napari_plugin_manager/qt_plugin_dialog.py @@ -40,29 +40,10 @@ from napari_plugin_manager.utils import is_conda_package try: - from napari.utils.task_status import ( - Status, - register_task_status, - update_task_status, - ) + from napari.utils.task_status import Status except ImportError: from napari_plugin_manager.base_qt_plugin_dialog import Status - def register_task_status( - provider: str, - task_status: Status, - description: str, - cancel_callback: Callable | None = None, - ) -> uuid.UUID: - pass - - def update_task_status( - task_status_id: uuid.UUID, - status: Status, - description: str = '', - ) -> bool: - pass - # Scaling factor for each list widget item when expanding. STYLES_PATH = Path(__file__).parent / 'styles.qss' @@ -300,12 +281,15 @@ def register_task_status( description: str, cancel_callback: Callable | None = None, ) -> uuid.UUID: - return register_task_status( - 'napari-plugin-manager', - task_status, - description, - cancel_callback=cancel_callback, - ) + window = getattr(self._parent, '_window', None) + if window and hasattr(window, '_register_task_status'): + return window._register_task_status( + 'napari-plugin-manager', + task_status, + description, + cancel_callback=cancel_callback, + ) + return None def update_task_status( self, @@ -313,9 +297,12 @@ def update_task_status( status: Status, description: str = '', ) -> bool: - return update_task_status( - task_status_id, status, description=description - ) + window = getattr(self._parent, '_window', None) + if window and hasattr(window, '_update_task_status'): + return window._update_task_status( + task_status_id, status, description=description + ) + return False def query_status(self) -> tuple[Status, str]: if self.installer.hasJobs():