Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a warning message when triggering an install action using PyPI source on a bundle/conda installation #111

Merged
merged 11 commits into from
Dec 4, 2024
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
Loading