Skip to content

Commit

Permalink
Add a warning message when triggering an install action using PyPI so…
Browse files Browse the repository at this point in the history
…urce on a bundle/conda installation (#111)

* Add a warning dialog when triggering an install action using PyPI source on a bundle installation

* Apply suggestions from code review

Co-authored-by: jaimergp <[email protected]>

* Apply suggestions from code review

Co-authored-by: Peter Sobolewski <[email protected]>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update docstring and a couple of missing changes to follow the suggestions

* Initial checkbox dismiss implementation

* Action validation method approach

* Missing return in the action validation approach

* Testing

---------

Co-authored-by: jaimergp <[email protected]>
Co-authored-by: Peter Sobolewski <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Dec 4, 2024
1 parent cccd4d1 commit 84f79a0
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 1 deletion.
30 changes: 30 additions & 0 deletions napari_plugin_manager/_tests/test_qt_plugin_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from napari.utils.translations import trans
from qtpy.QtCore import QMimeData, QPointF, Qt, QUrl
from qtpy.QtGui import QDropEvent
from qtpy.QtWidgets import QMessageBox

if qtpy.API_NAME == 'PySide2' and sys.version_info[:2] > (3, 10):
pytest.skip(
Expand Down Expand Up @@ -484,6 +485,35 @@ def test_installs(qtbot, tmp_virtualenv, plugin_dialog, request):
qtbot.wait(5000)


@pytest.mark.parametrize(
"message_return",
[QMessageBox.StandardButton.Cancel, QMessageBox.StandardButton.Ok],
)
def test_install_pypi_constructor(
qtbot, tmp_virtualenv, plugin_dialog, request, message_return
):
if "no-constructor" in request.node.name:
pytest.skip(
reason="This test is only relevant for constructor-based installs"
)

plugin_dialog.set_prefix(str(tmp_virtualenv))
plugin_dialog.search('requests')
qtbot.wait(500)
item = plugin_dialog.available_list.item(0)
widget = plugin_dialog.available_list.itemWidget(item)
with patch.object(qt_plugin_dialog.QMessageBox, "exec_") as mock:
mock.return_value = message_return
if message_return == QMessageBox.StandardButton.Ok:
with qtbot.waitSignal(
plugin_dialog.installer.processFinished, timeout=60_000
):
widget.action_button.click()
else:
widget.action_button.click()
assert mock.called


def test_cancel(qtbot, tmp_virtualenv, plugin_dialog, request):
if "[constructor]" in request.node.name:
pytest.skip(
Expand Down
19 changes: 18 additions & 1 deletion napari_plugin_manager/base_qt_plugin_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,20 @@ def _on_enabled_checkbox(self, state: Qt.CheckState) -> None:
"""
raise NotImplementedError

def _action_validation(self, tool, action) -> bool:
"""
Validate if the current action should be done or not.
As an example you could warn that a package from PyPI is going
to be installed.
Returns
-------
This should return a `bool`, `True` if the action should proceed, `False`
otherwise.
"""
raise NotImplementedError

def _cancel_requested(self):
version = self.version_choice_dropdown.currentText()
tool = self.get_installer_tool()
Expand All @@ -615,7 +629,10 @@ def _action_requested(self):
if self.action_button.objectName() == 'install_button'
else InstallerActions.UNINSTALL
)
self.actionRequested.emit(self.item, self.name, action, version, tool)
if self._action_validation(tool, action):
self.actionRequested.emit(
self.item, self.name, action, version, tool
)

def _update_requested(self):
version = self.version_choice_dropdown.currentText()
Expand Down
46 changes: 46 additions & 0 deletions napari_plugin_manager/qt_plugin_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from qtpy.QtGui import (
QMovie,
)
from qtpy.QtWidgets import QCheckBox, QMessageBox

from napari_plugin_manager.base_qt_plugin_dialog import (
BasePluginListItem,
Expand All @@ -32,10 +33,13 @@
)
from napari_plugin_manager.qt_package_installer import (
InstallerActions,
InstallerTools,
)
from napari_plugin_manager.utils import is_conda_package

# Scaling factor for each list widget item when expanding.
STYLES_PATH = Path(__file__).parent / 'styles.qss'
DISMISS_WARN_PYPI_INSTALL_DLG = False


def _show_message(widget):
Expand Down Expand Up @@ -127,6 +131,48 @@ def _on_enabled_checkbox(self, state: int):
)
return

def _warn_pypi_install(self):
return running_as_constructor_app() or is_conda_package(
'napari'
) # or True

def _action_validation(self, tool, action):
global DISMISS_WARN_PYPI_INSTALL_DLG
if (
tool == InstallerTools.PIP
and action == InstallerActions.INSTALL
and self._warn_pypi_install()
and not DISMISS_WARN_PYPI_INSTALL_DLG
):
warn_msgbox = QMessageBox(self)
warn_msgbox.setWindowTitle(
self._trans('PyPI installation on bundle/conda')
)
warn_msgbox.setText(
self._trans(
'Installing from PyPI does not take into account existing installed packages, '
'so it can break existing installations. '
'If this happens the only solution is to reinstall the bundle/create a new conda environment.\n\n'
'Are you sure you want to install from PyPI?'
)
)
warn_checkbox = QCheckBox(
self._trans(
"Don't show this message again in the current session"
)
)
warn_msgbox.setCheckBox(warn_checkbox)
warn_msgbox.setIcon(QMessageBox.Icon.Warning)
warn_msgbox.setStandardButtons(
QMessageBox.StandardButton.Ok
| QMessageBox.StandardButton.Cancel
)
button_clicked = warn_msgbox.exec_()
DISMISS_WARN_PYPI_INSTALL_DLG = warn_checkbox.isChecked()
if button_clicked != QMessageBox.StandardButton.Ok:
return False
return True


class QPluginList(BaseQPluginList):

Expand Down

0 comments on commit 84f79a0

Please sign in to comment.