Skip to content

Commit

Permalink
Merge pull request napari#120 from goanpeca/enh/export-list
Browse files Browse the repository at this point in the history
Add import/export functionality and buttons on ui
  • Loading branch information
goanpeca authored Jan 8, 2025
2 parents acb327b + bd387ac commit 34d3fa9
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 11 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,19 @@ You can cancel the process at any time by clicking the `Cancel` button of each p

![Screenshot of the napari-plugin-manager showing the process of updating a plugin](https://raw.githubusercontent.com/napari/napari-plugin-manager/main/images/update.png)

### Export/Import plugins

You can export the list of install plugins by clicking on the `Export` button located on the top right
corner of the UI. This will prompt a dialog to select the location and name of the text file where
the installed plugin list will be saved.

This file can be shared and then imported by clicking on the `Import` button located on the top right
corner of the UI. This will prompt a dialog to select the location of the text file to import.

After selecting the file, the plugin will dialog will attempt to install all the listed plugins.

![Screenshot of the napari-plugin-manager showing the process of import/export](https://raw.githubusercontent.com/napari/napari-plugin-manager/main/images/import-export.png)

### Batch actions

You don't need wait for one action to finish before you can start another one. You can add more
Expand Down
Binary file added images/import-export.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 17 additions & 9 deletions napari_plugin_manager/_tests/test_npe2api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from urllib.error import HTTPError, URLError

from flaky import flaky

from napari_plugin_manager.npe2api import (
Expand Down Expand Up @@ -26,20 +28,26 @@ def test_plugin_summaries():
"pypi_versions",
"conda_versions",
]
data = plugin_summaries()
test_data = dict(data[0])
for key in keys:
assert key in test_data
test_data.pop(key)
try:
data = plugin_summaries()
test_data = dict(data[0])
for key in keys:
assert key in test_data
test_data.pop(key)

assert not test_data
assert not test_data
except (HTTPError, URLError):
pass


def test_conda_map():
pkgs = ["napari-svg"]
data = conda_map()
for pkg in pkgs:
assert pkg in data
try:
data = conda_map()
for pkg in pkgs:
assert pkg in data
except (HTTPError, URLError):
pass


def test_iter_napari_plugin_info():
Expand Down
50 changes: 48 additions & 2 deletions napari_plugin_manager/_tests/test_qt_plugin_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
import qtpy
from napari.plugins._tests.test_npe2 import mock_pm # noqa
from napari.utils.translations import trans
from qtpy.QtCore import QMimeData, QPointF, Qt, QUrl
from qtpy.QtCore import QMimeData, QPointF, Qt, QTimer, QUrl
from qtpy.QtGui import QDropEvent
from qtpy.QtWidgets import QMessageBox
from qtpy.QtWidgets import (
QApplication,
QMessageBox,
)

if qtpy.API_NAME == 'PySide2' and sys.version_info[:2] > (3, 10):
pytest.skip(
Expand Down Expand Up @@ -605,3 +608,46 @@ def test_shortcut_quit(plugin_dialog, qtbot):
)
qtbot.wait(500)
assert not plugin_dialog.isVisible()


@pytest.mark.skipif(
not sys.platform.startswith('linux'), reason="Test works only on linux"
)
def test_export_plugins_button(plugin_dialog):
def _timer():
dialog = QApplication.activeModalWidget()
dialog.reject()

timer = QTimer()
timer.setSingleShot(True)
timer.timeout.connect(_timer)
timer.start(4_000)
plugin_dialog.export_button.click()


def test_export_plugins(plugin_dialog, tmp_path):
plugins_file = 'plugins.txt'
plugin_dialog.export_plugins(str(tmp_path / plugins_file))
assert (tmp_path / plugins_file).exists()


@pytest.mark.skipif(
not sys.platform.startswith('linux'), reason="Test works only on linux"
)
def test_import_plugins_button(plugin_dialog):
def _timer():
dialog = QApplication.activeModalWidget()
dialog.reject()

timer = QTimer()
timer.setSingleShot(True)
timer.timeout.connect(_timer)
timer.start(4_000)
plugin_dialog.import_button.click()


def test_import_plugins(plugin_dialog, tmp_path, qtbot):
path = tmp_path / 'plugins.txt'
path.write_text('requests\npyzenhub\n')
with qtbot.waitSignal(plugin_dialog.installer.allFinished, timeout=60_000):
plugin_dialog.import_plugins(str(path))
49 changes: 49 additions & 0 deletions napari_plugin_manager/base_qt_plugin_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
)

from packaging.version import parse as parse_version
from qtpy.compat import getopenfilename, getsavefilename
from qtpy.QtCore import QSize, Qt, QTimer, Signal, Slot
from qtpy.QtGui import (
QAction,
Expand Down Expand Up @@ -1385,6 +1386,16 @@ def _setup_ui(self):
self.packages_search.setClearButtonEnabled(True)
self.packages_search.textChanged.connect(self.search)

self.import_button = QPushButton(self._trans('Import'), self)
self.import_button.setObjectName("import_button")
self.import_button.setToolTip(self._trans(''))
self.import_button.clicked.connect(self._import_plugins)

self.export_button = QPushButton(self._trans('Export'), self)
self.export_button.setObjectName("export_button")
self.export_button.setToolTip(self._trans(''))
self.export_button.clicked.connect(self._export_plugins)

self.refresh_button = QPushButton(self._trans('Refresh'), self)
self.refresh_button.setObjectName("refresh_button")
self.refresh_button.setToolTip(
Expand All @@ -1398,6 +1409,8 @@ def _setup_ui(self):
horizontal_mid_layout = QHBoxLayout()
horizontal_mid_layout.addWidget(self.packages_search)
horizontal_mid_layout.addStretch()
horizontal_mid_layout.addWidget(self.import_button)
horizontal_mid_layout.addWidget(self.export_button)
horizontal_mid_layout.addWidget(self.refresh_button)
mid_layout.addLayout(horizontal_mid_layout)
mid_layout.addWidget(self.installed_label)
Expand Down Expand Up @@ -1687,6 +1700,16 @@ def _search_in_available(self, text):
def _refresh_and_clear_cache(self):
self.refresh(clear_cache=True)

def _import_plugins(self):
fpath, _ = getopenfilename(filters="Text files (*.txt)")
if fpath:
self.import_plugins(fpath)

def _export_plugins(self):
fpath, _ = getsavefilename(filters="Text files (*.txt)")
if fpath:
self.export_plugins(fpath)

# endregion - Private methods

# region - Qt overrides
Expand Down Expand Up @@ -1813,4 +1836,30 @@ def set_prefix(self, prefix):
item = self.installed_list.item(idx)
item.widget.prefix = prefix

def export_plugins(self, fpath: str) -> list[str]:
"""Export installed plugins to a file."""
plugins = []
if self.installed_list.count():
for idx in range(self.installed_list.count()):
item = self.installed_list.item(idx)
if item:
name = item.widget.name
version = item.widget._version # Make public attr?
plugins.append(f"{name}=={version}\n")

with open(fpath, 'w') as f:
f.writelines(plugins)

return plugins

def import_plugins(self, fpath: str) -> None:
"""Install plugins from file."""
with open(fpath) as f:
plugins = f.read().split('\n')

print(plugins)

plugins = [p for p in plugins if p]
self._install_packages(plugins)

# endregion - Public methods

0 comments on commit 34d3fa9

Please sign in to comment.