napari-plugin-manager 0.1.6__py3-none-any.whl → 0.1.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,8 +2,9 @@ import contextlib
2
2
  import importlib.metadata
3
3
  import os
4
4
  import webbrowser
5
- from collections.abc import Sequence
5
+ from collections.abc import Iterable, Sequence
6
6
  from functools import partial
7
+ from logging import getLogger
7
8
  from typing import (
8
9
  Any,
9
10
  Literal,
@@ -17,7 +18,11 @@ from qtpy.QtCore import QSize, Qt, QTimer, Signal, Slot
17
18
  from qtpy.QtGui import (
18
19
  QAction,
19
20
  QActionGroup,
21
+ QCloseEvent,
22
+ QDropEvent,
23
+ QEnterEvent,
20
24
  QFont,
25
+ QHideEvent,
21
26
  QIcon,
22
27
  QKeySequence,
23
28
  QMovie,
@@ -38,6 +43,7 @@ from qtpy.QtWidgets import (
38
43
  QPushButton,
39
44
  QSizePolicy,
40
45
  QSplitter,
46
+ QStackedWidget,
41
47
  QTextEdit,
42
48
  QToolButton,
43
49
  QVBoxLayout,
@@ -57,6 +63,8 @@ from napari_plugin_manager.utils import get_homepage_url, is_conda_package
57
63
 
58
64
  CONDA = 'Conda'
59
65
  PYPI = 'PyPI'
66
+ PACKAGE_SOURCES = Literal['PyPI', 'Conda']
67
+ log = getLogger(__name__)
60
68
 
61
69
 
62
70
  class PackageMetadataProtocol(Protocol):
@@ -142,13 +150,13 @@ class BasePluginListItem(QFrame):
142
150
  url: str = '',
143
151
  summary: str = '',
144
152
  author: str = '',
145
- license: str = "UNKNOWN", # noqa: A002
153
+ license: str = 'UNKNOWN', # noqa: A002
146
154
  *,
147
155
  plugin_name: str | None = None,
148
156
  parent: QWidget = None,
149
157
  enabled: bool = True,
150
158
  installed: bool = False,
151
- plugin_api_version=1,
159
+ plugin_api_version: int | None = 1,
152
160
  versions_conda: list[str] | None = None,
153
161
  versions_pypi: list[str] | None = None,
154
162
  prefix=None,
@@ -160,14 +168,14 @@ class BasePluginListItem(QFrame):
160
168
  self.name = package_name
161
169
  self.plugin_api_version = plugin_api_version
162
170
  self._version = version
163
- self._versions_conda = versions_conda
164
- self._versions_pypi = versions_pypi
171
+ self._versions_conda = versions_conda or []
172
+ self._versions_pypi = versions_pypi or []
165
173
  self.setup_ui(enabled)
166
174
 
167
175
  if package_name == display_name:
168
176
  name = package_name
169
177
  else:
170
- name = f"{display_name} <small>({package_name})</small>"
178
+ name = f'{display_name} <small>({package_name})</small>'
171
179
 
172
180
  self.plugin_name.setText(name)
173
181
 
@@ -255,24 +263,24 @@ class BasePluginListItem(QFrame):
255
263
  """
256
264
  raise NotImplementedError
257
265
 
258
- def _is_main_app_conda_package(self):
266
+ def _is_main_app_conda_package(self) -> bool:
259
267
  return is_conda_package(self.BASE_PACKAGE_NAME)
260
268
 
261
- def _set_installed(self, installed: bool, package_name):
269
+ def _set_installed(self, installed: bool, package_name) -> None:
262
270
  if installed:
263
271
  if is_conda_package(package_name):
264
272
  self.source.setText(CONDA)
265
273
 
266
274
  self.enabled_checkbox.show()
267
- self.action_button.setText(self._trans("Uninstall"))
268
- self.action_button.setObjectName("remove_button")
275
+ self.action_button.setText(self._trans('Uninstall'))
276
+ self.action_button.setObjectName('remove_button')
269
277
  self.info_choice_wdg.hide()
270
278
  self.install_info_button.addWidget(self.info_widget)
271
279
  self.info_widget.show()
272
280
  else:
273
281
  self.enabled_checkbox.hide()
274
- self.action_button.setText(self._trans("Install"))
275
- self.action_button.setObjectName("install_button")
282
+ self.action_button.setText(self._trans('Install'))
283
+ self.action_button.setObjectName('install_button')
276
284
  self.info_widget.hide()
277
285
  self.install_info_button.addWidget(self.info_choice_wdg)
278
286
  self.info_choice_wdg.show()
@@ -292,7 +300,7 @@ class BasePluginListItem(QFrame):
292
300
  """
293
301
  raise NotImplementedError
294
302
 
295
- def set_status(self, icon=None, text=''):
303
+ def set_status(self, icon=None, text='') -> None:
296
304
  """Set the status icon and text next to the package name."""
297
305
  if icon:
298
306
  self.status_icon.setPixmap(icon)
@@ -309,7 +317,7 @@ class BasePluginListItem(QFrame):
309
317
  action_name: (
310
318
  Literal['install', 'uninstall', 'cancel', 'upgrade'] | None
311
319
  ) = None,
312
- ):
320
+ ) -> None:
313
321
  """Updates status text and what buttons are visible when any button is pushed.
314
322
 
315
323
  Parameters
@@ -332,18 +340,18 @@ class BasePluginListItem(QFrame):
332
340
  self.action_button.setDisabled(False)
333
341
  self.cancel_btn.setVisible(False)
334
342
  else: # pragma: no cover
335
- raise ValueError(f"Not supported {action_name}")
343
+ raise ValueError(f'Not supported {action_name}')
336
344
 
337
- def is_busy(self):
345
+ def is_busy(self) -> bool:
338
346
  return bool(self.item_status.text())
339
347
 
340
- def setup_ui(self, enabled=True):
348
+ def setup_ui(self, enabled: bool = True) -> None:
341
349
  """Define the layout of the PluginListItem"""
342
350
  # Enabled checkbox
343
351
  self.enabled_checkbox = QCheckBox(self)
344
352
  self.enabled_checkbox.setChecked(enabled)
345
- self.enabled_checkbox.setToolTip(self._trans("enable/disable"))
346
- self.enabled_checkbox.setText("")
353
+ self.enabled_checkbox.setToolTip(self._trans('enable/disable'))
354
+ self.enabled_checkbox.setText('')
347
355
  self.enabled_checkbox.stateChanged.connect(self._on_enabled_checkbox)
348
356
 
349
357
  sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
@@ -391,7 +399,7 @@ class BasePluginListItem(QFrame):
391
399
 
392
400
  # Item status
393
401
  self.item_status = QLabel(self)
394
- self.item_status.setObjectName("small_italic_text")
402
+ self.item_status.setObjectName('small_italic_text')
395
403
  self.item_status.setSizePolicy(sizePolicy)
396
404
 
397
405
  # Summary
@@ -407,7 +415,7 @@ class BasePluginListItem(QFrame):
407
415
  sizePolicy.setHorizontalStretch(1)
408
416
  sizePolicy.setVerticalStretch(0)
409
417
  self.summary.setSizePolicy(sizePolicy)
410
- self.summary.setContentsMargins(0, -2, 0, -2)
418
+ self.summary.setContentsMargins(0, 0, 0, 0)
411
419
 
412
420
  # Package author
413
421
  self.package_author = QElidingLabel(self)
@@ -417,7 +425,7 @@ class BasePluginListItem(QFrame):
417
425
 
418
426
  # Update button
419
427
  self.update_btn = QPushButton('Update', self)
420
- self.update_btn.setObjectName("install_button")
428
+ self.update_btn.setObjectName('install_button')
421
429
  self.update_btn.setVisible(False)
422
430
  self.update_btn.clicked.connect(self._update_requested)
423
431
  sizePolicy.setRetainSizeWhenHidden(True)
@@ -427,16 +435,16 @@ class BasePluginListItem(QFrame):
427
435
 
428
436
  # Action Button
429
437
  self.action_button = QPushButton(self)
430
- self.action_button.setFixedWidth(70)
438
+ self.action_button.setFixedWidth(80)
431
439
  sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
432
440
  self.action_button.setSizePolicy(sizePolicy1)
433
441
  self.action_button.clicked.connect(self._action_requested)
434
442
 
435
443
  # Cancel
436
- self.cancel_btn = QPushButton("Cancel", self)
437
- self.cancel_btn.setObjectName("remove_button")
444
+ self.cancel_btn = QPushButton('Cancel', self)
445
+ self.cancel_btn.setObjectName('remove_button')
438
446
  self.cancel_btn.setSizePolicy(sizePolicy)
439
- self.cancel_btn.setFixedWidth(70)
447
+ self.cancel_btn.setFixedWidth(80)
440
448
  self.cancel_btn.clicked.connect(self._cancel_requested)
441
449
 
442
450
  # Collapsible button
@@ -444,12 +452,12 @@ class BasePluginListItem(QFrame):
444
452
  exp_icon = self._expanded_icon()
445
453
 
446
454
  self.install_info_button = QCollapsible(
447
- "Installation Info", collapsedIcon=coll_icon, expandedIcon=exp_icon
455
+ 'Installation Info', collapsedIcon=coll_icon, expandedIcon=exp_icon
448
456
  )
449
457
  self.install_info_button.setLayoutDirection(
450
458
  Qt.RightToLeft
451
459
  ) # Make icon appear on the right
452
- self.install_info_button.setObjectName("install_info_button")
460
+ self.install_info_button.setObjectName('install_info_button')
453
461
  self.install_info_button.setFixedWidth(180)
454
462
  self.install_info_button.content().layout().setContentsMargins(
455
463
  0, 0, 0, 0
@@ -485,7 +493,7 @@ class BasePluginListItem(QFrame):
485
493
  # Information widget for installed packages
486
494
  self.info_widget = QWidget(self)
487
495
  self.info_widget.setLayoutDirection(Qt.LeftToRight)
488
- self.info_widget.setObjectName("info_widget")
496
+ self.info_widget.setObjectName('info_widget')
489
497
  self.info_widget.setFixedWidth(180)
490
498
 
491
499
  self.source_text = QLabel('Source:')
@@ -505,7 +513,7 @@ class BasePluginListItem(QFrame):
505
513
 
506
514
  # Error indicator
507
515
  self.error_indicator = QPushButton()
508
- self.error_indicator.setObjectName("warning_icon")
516
+ self.error_indicator.setObjectName('warning_icon')
509
517
  self.error_indicator.setCursor(Qt.CursorShape.PointingHandCursor)
510
518
  self.error_indicator.hide()
511
519
 
@@ -568,10 +576,10 @@ class BasePluginListItem(QFrame):
568
576
 
569
577
  self.info_choice_wdg.setLayout(info_layout)
570
578
  self.info_choice_wdg.setLayoutDirection(Qt.LeftToRight)
571
- self.info_choice_wdg.setObjectName("install_choice_widget")
579
+ self.info_choice_wdg.setObjectName('install_choice_widget')
572
580
  self.info_choice_wdg.hide()
573
581
 
574
- def _populate_version_dropdown(self, source: Literal["PyPI", "Conda"]):
582
+ def _populate_version_dropdown(self, source: PACKAGE_SOURCES) -> None:
575
583
  """Display the versions available after selecting a source: pypi or conda."""
576
584
  if source == PYPI:
577
585
  versions = self._versions_pypi
@@ -612,14 +620,14 @@ class BasePluginListItem(QFrame):
612
620
  """
613
621
  raise NotImplementedError
614
622
 
615
- def _cancel_requested(self):
623
+ def _cancel_requested(self) -> None:
616
624
  version = self.version_choice_dropdown.currentText()
617
625
  tool = self.get_installer_tool()
618
626
  self.actionRequested.emit(
619
627
  self.item, self.name, InstallerActions.CANCEL, version, tool
620
628
  )
621
629
 
622
- def _action_requested(self):
630
+ def _action_requested(self) -> None:
623
631
  version = self.version_choice_dropdown.currentText()
624
632
  tool = self.get_installer_tool()
625
633
  action = (
@@ -632,19 +640,19 @@ class BasePluginListItem(QFrame):
632
640
  self.item, self.name, action, version, tool
633
641
  )
634
642
 
635
- def _update_requested(self):
643
+ def _update_requested(self) -> None:
636
644
  version = self.version_choice_dropdown.currentText()
637
645
  tool = self.get_installer_tool()
638
646
  self.actionRequested.emit(
639
647
  self.item, self.name, InstallerActions.UPGRADE, version, tool
640
648
  )
641
649
 
642
- def show_warning(self, message: str = ""):
650
+ def show_warning(self, message: str = '') -> None:
643
651
  """Show warning icon and tooltip."""
644
652
  self.warning_tooltip.setVisible(bool(message))
645
653
  self.warning_tooltip.setToolTip(message)
646
654
 
647
- def get_installer_source(self):
655
+ def get_installer_source(self) -> Literal['Conda', 'PyPI']:
648
656
  return (
649
657
  CONDA
650
658
  if self.source_choice_dropdown.currentText() == CONDA
@@ -652,12 +660,12 @@ class BasePluginListItem(QFrame):
652
660
  else PYPI
653
661
  )
654
662
 
655
- def get_installer_tool(self):
663
+ def get_installer_tool(self) -> InstallerTools:
656
664
  return (
657
665
  InstallerTools.CONDA
658
666
  if self.source_choice_dropdown.currentText() == CONDA
659
667
  or is_conda_package(self.name, prefix=self.prefix)
660
- else InstallerTools.PIP
668
+ else InstallerTools.PYPI
661
669
  )
662
670
 
663
671
 
@@ -719,11 +727,11 @@ class BaseQPluginList(QListWidget):
719
727
  def addItem(
720
728
  self,
721
729
  project_info: BaseProjectInfoVersions,
722
- installed=False,
723
- plugin_name=None,
724
- enabled=True,
725
- plugin_api_version=None,
726
- ):
730
+ installed: bool = False,
731
+ plugin_name: str | None = None,
732
+ enabled: bool = True,
733
+ plugin_api_version: int | None = None,
734
+ ) -> None:
727
735
  pkg_name = project_info.metadata.name
728
736
  # don't add duplicates
729
737
  if (
@@ -733,7 +741,7 @@ class BaseQPluginList(QListWidget):
733
741
  return
734
742
 
735
743
  # including summary here for sake of filtering below.
736
- searchable_text = f"{pkg_name} {project_info.display_name} {project_info.metadata.summary}"
744
+ searchable_text = f'{pkg_name} {project_info.display_name} {project_info.metadata.summary}'
737
745
  item = QListWidgetItem(searchable_text, self)
738
746
  item.version = project_info.metadata.version
739
747
  super().addItem(item)
@@ -774,7 +782,7 @@ class BaseQPluginList(QListWidget):
774
782
  lambda: self._resize_pluginlistitem(item)
775
783
  )
776
784
 
777
- def removeItem(self, name):
785
+ def removeItem(self, name: str) -> None:
778
786
  count = self.count()
779
787
  for i in range(count):
780
788
  item = self.item(i)
@@ -782,7 +790,7 @@ class BaseQPluginList(QListWidget):
782
790
  self.takeItem(i)
783
791
  break
784
792
 
785
- def refreshItem(self, name, version=None):
793
+ def refreshItem(self, name: str, version: str | None = None) -> None:
786
794
  count = self.count()
787
795
  for i in range(count):
788
796
  item = self.item(i)
@@ -797,7 +805,7 @@ class BaseQPluginList(QListWidget):
797
805
  item.setText(item.text()[len(self._SORT_ORDER_PREFIX) :])
798
806
  break
799
807
 
800
- def _resize_pluginlistitem(self, item):
808
+ def _resize_pluginlistitem(self, item: QListWidgetItem):
801
809
  """Resize the plugin list item, especially after toggling QCollapsible."""
802
810
  if item.widget.install_info_button.isExpanded():
803
811
  item.widget.setFixedHeight(self._initial_height + 35)
@@ -830,39 +838,36 @@ class BaseQPluginList(QListWidget):
830
838
  pkg_name: str,
831
839
  action_name: InstallerActions,
832
840
  version: str | None = None,
833
- installer_choice: str | None = None,
834
- ):
841
+ installer_choice: InstallerTools | None = None,
842
+ ) -> None:
835
843
  """Determine which action is called (install, uninstall, update, cancel).
836
844
  Update buttons appropriately and run the action."""
837
845
  widget = item.widget
838
846
  tool = installer_choice or widget.get_installer_tool()
839
847
  self._remove_list.append((pkg_name, item))
840
- self._warn_dialog = None
841
848
  if not item.text().startswith(self._SORT_ORDER_PREFIX):
842
- item.setText(f"{self._SORT_ORDER_PREFIX}{item.text()}")
849
+ item.setText(f'{self._SORT_ORDER_PREFIX}{item.text()}')
843
850
 
844
851
  if action_name == InstallerActions.INSTALL:
845
852
  if version:
846
853
  pkg_name += (
847
- f"=={item.widget.version_choice_dropdown.currentText()}"
854
+ f'=={item.widget.version_choice_dropdown.currentText()}'
848
855
  )
849
- widget.set_busy(self._trans("installing..."), action_name)
856
+ widget.set_busy(self._trans('installing...'), action_name)
850
857
 
851
858
  job_id = self.installer.install(
852
859
  tool=tool,
853
860
  pkgs=[pkg_name],
854
861
  # origins="TODO",
855
862
  )
856
- widget.setProperty("current_job_id", job_id)
857
- if self._warn_dialog:
858
- self._warn_dialog.exec_()
863
+ widget.setProperty('current_job_id', job_id)
859
864
  self.scrollToTop()
860
865
 
861
866
  if action_name == InstallerActions.UPGRADE:
862
867
  if hasattr(item, 'latest_version'):
863
- pkg_name += f"=={item.latest_version}"
868
+ pkg_name += f'=={item.latest_version}'
864
869
 
865
- widget.set_busy(self._trans("updating..."), action_name)
870
+ widget.set_busy(self._trans('updating...'), action_name)
866
871
  widget.update_btn.setDisabled(True)
867
872
  widget.action_button.setDisabled(True)
868
873
 
@@ -871,13 +876,11 @@ class BaseQPluginList(QListWidget):
871
876
  pkgs=[pkg_name],
872
877
  # origins="TODO",
873
878
  )
874
- widget.setProperty("current_job_id", job_id)
875
- if self._warn_dialog:
876
- self._warn_dialog.exec_()
879
+ widget.setProperty('current_job_id', job_id)
877
880
  self.scrollToTop()
878
881
 
879
882
  elif action_name == InstallerActions.UNINSTALL:
880
- widget.set_busy(self._trans("uninstalling..."), action_name)
883
+ widget.set_busy(self._trans('uninstalling...'), action_name)
881
884
  widget.update_btn.setDisabled(True)
882
885
  job_id = self.installer.uninstall(
883
886
  tool=tool,
@@ -885,31 +888,29 @@ class BaseQPluginList(QListWidget):
885
888
  # origins="TODO",
886
889
  # upgrade=False,
887
890
  )
888
- widget.setProperty("current_job_id", job_id)
889
- if self._warn_dialog:
890
- self._warn_dialog.exec_()
891
+ widget.setProperty('current_job_id', job_id)
891
892
  self.scrollToTop()
892
893
  elif action_name == InstallerActions.CANCEL:
893
- widget.set_busy(self._trans("cancelling..."), action_name)
894
+ widget.set_busy(self._trans('cancelling...'), action_name)
894
895
  try:
895
- job_id = widget.property("current_job_id")
896
+ job_id = widget.property('current_job_id')
896
897
  self.installer.cancel(job_id)
897
898
  finally:
898
- widget.setProperty("current_job_id", None)
899
+ widget.setProperty('current_job_id', None)
899
900
 
900
- def set_data(self, data):
901
+ def set_data(self, data) -> None:
901
902
  self._data = data
902
903
 
903
- def is_running(self):
904
+ def is_running(self) -> bool:
904
905
  return self.count() != len(self._data)
905
906
 
906
- def packages(self):
907
+ def packages(self) -> list[str]:
907
908
  return [self.item(idx).widget.name for idx in range(self.count())]
908
909
 
909
910
  @Slot(PackageMetadataProtocol, bool)
910
911
  def tag_outdated(
911
912
  self, metadata: PackageMetadataProtocol, is_available: bool
912
- ):
913
+ ) -> None:
913
914
  """Determines if an installed plugin is up to date with the latest version.
914
915
  If it is not, the latest version will be displayed on the update button.
915
916
  """
@@ -940,10 +941,10 @@ class BaseQPluginList(QListWidget):
940
941
  widg = self.itemWidget(item)
941
942
  widg.update_btn.setVisible(True)
942
943
  widg.update_btn.setText(
943
- self._trans("update (v{latest})", latest=latest)
944
+ self._trans('update (v{latest})', latest=latest)
944
945
  )
945
946
 
946
- def tag_unavailable(self, metadata: PackageMetadataProtocol):
947
+ def tag_unavailable(self, metadata: PackageMetadataProtocol) -> None:
947
948
  """
948
949
  Tag list items as unavailable for install with conda-forge.
949
950
 
@@ -956,16 +957,16 @@ class BaseQPluginList(QListWidget):
956
957
  widget = self.itemWidget(item)
957
958
  widget.show_warning(
958
959
  self._trans(
959
- "Plugin not yet available for installation within the bundle application"
960
+ 'Plugin not yet available for installation within the bundle application'
960
961
  )
961
962
  )
962
- widget.setObjectName("unavailable")
963
+ widget.setObjectName('unavailable')
963
964
  widget.style().unpolish(widget)
964
965
  widget.style().polish(widget)
965
966
  widget.action_button.setEnabled(False)
966
967
  widget.warning_tooltip.setVisible(True)
967
968
 
968
- def filter(self, text: str, starts_with_chars: int = 1):
969
+ def filter(self, text: str, starts_with_chars: int = 1) -> None:
969
970
  """Filter items to those containing `text`."""
970
971
  if text:
971
972
  # PySide has some issues, so we compare using id
@@ -997,7 +998,7 @@ class BaseQPluginList(QListWidget):
997
998
  item = self.item(i)
998
999
  item.setHidden(False)
999
1000
 
1000
- def hideAll(self):
1001
+ def hideAll(self) -> None:
1001
1002
  for i in range(self.count()):
1002
1003
  item = self.item(i)
1003
1004
  item.setHidden(not item.widget.is_busy())
@@ -1027,7 +1028,7 @@ class BaseQtPluginDialog(QDialog):
1027
1028
 
1028
1029
  finished = Signal()
1029
1030
 
1030
- def __init__(self, parent=None, prefix=None) -> None:
1031
+ def __init__(self, parent: QDialog = None, prefix=None) -> None:
1031
1032
  super().__init__(parent)
1032
1033
 
1033
1034
  self._parent = parent
@@ -1040,6 +1041,7 @@ class BaseQtPluginDialog(QDialog):
1040
1041
  self._plugins_found = 0
1041
1042
  self.already_installed = set()
1042
1043
  self.available_set = set()
1044
+ self.modified_set = set()
1043
1045
  self._prefix = prefix
1044
1046
  self._first_open = True
1045
1047
  self._plugin_queue = [] # Store plugin data to be added
@@ -1080,15 +1082,15 @@ class BaseQtPluginDialog(QDialog):
1080
1082
 
1081
1083
  # region - Private methods
1082
1084
  # ------------------------------------------------------------------------
1083
- def _enable_refresh_button(self):
1085
+ def _enable_refresh_button(self) -> None:
1084
1086
  self.refresh_button.setEnabled(True)
1085
1087
 
1086
- def _quit(self):
1088
+ def _quit(self) -> None:
1087
1089
  self.close()
1088
1090
  with contextlib.suppress(AttributeError):
1089
1091
  self._parent.close(quit_app=True, confirm_need=True)
1090
1092
 
1091
- def _setup_shortcuts(self):
1093
+ def _setup_shortcuts(self) -> None:
1092
1094
  self._refresh_styles_action = QAction(
1093
1095
  self._trans('Refresh Styles'), self
1094
1096
  )
@@ -1128,7 +1130,7 @@ class BaseQtPluginDialog(QDialog):
1128
1130
  """
1129
1131
  raise NotImplementedError
1130
1132
 
1131
- def _on_installer_start(self):
1133
+ def _on_installer_start(self) -> None:
1132
1134
  """Updates dialog buttons and status when installing a plugin."""
1133
1135
  self.cancel_all_btn.setVisible(True)
1134
1136
  self.working_indicator.show()
@@ -1136,7 +1138,9 @@ class BaseQtPluginDialog(QDialog):
1136
1138
  self.process_error_indicator.hide()
1137
1139
  self.refresh_button.setDisabled(True)
1138
1140
 
1139
- def _on_process_finished(self, process_finished_data: ProcessFinishedData):
1141
+ def _on_process_finished(
1142
+ self, process_finished_data: ProcessFinishedData
1143
+ ) -> None:
1140
1144
  action = process_finished_data['action']
1141
1145
  exit_code = process_finished_data['exit_code']
1142
1146
  pkg_names = [
@@ -1150,6 +1154,7 @@ class BaseQtPluginDialog(QDialog):
1150
1154
 
1151
1155
  self.available_list.removeItem(pkg_name)
1152
1156
  self._add_installed(pkg_name)
1157
+ self.modified_set.add(pkg_name)
1153
1158
  self._tag_outdated_plugins()
1154
1159
  else:
1155
1160
  for pkg_name in pkg_names:
@@ -1162,6 +1167,7 @@ class BaseQtPluginDialog(QDialog):
1162
1167
 
1163
1168
  self.installed_list.removeItem(pkg_name)
1164
1169
  self._add_to_available(pkg_name)
1170
+ self.modified_set.add(pkg_name)
1165
1171
  else:
1166
1172
  for pkg_name in pkg_names:
1167
1173
  self.installed_list.refreshItem(pkg_name)
@@ -1175,6 +1181,7 @@ class BaseQtPluginDialog(QDialog):
1175
1181
  self.installed_list.refreshItem(
1176
1182
  pkg_name, version=pkg_version
1177
1183
  )
1184
+ self.modified_set.add(pkg_name)
1178
1185
  else:
1179
1186
  self.installed_list.refreshItem(pkg)
1180
1187
  self._tag_outdated_plugins()
@@ -1190,7 +1197,7 @@ class BaseQtPluginDialog(QDialog):
1190
1197
  else:
1191
1198
  self.process_success_indicator.show()
1192
1199
 
1193
- def _on_installer_all_finished(self, exit_codes):
1200
+ def _on_installer_all_finished(self, exit_codes: Iterable[int]) -> None:
1194
1201
  self.working_indicator.hide()
1195
1202
  self.cancel_all_btn.setVisible(False)
1196
1203
  self.close_btn.setDisabled(False)
@@ -1211,8 +1218,12 @@ class BaseQtPluginDialog(QDialog):
1211
1218
  self.search()
1212
1219
 
1213
1220
  def _add_to_installed(
1214
- self, distname, enabled, norm_name, plugin_api_version=1
1215
- ):
1221
+ self,
1222
+ distname: str,
1223
+ enabled: bool,
1224
+ norm_name: str,
1225
+ plugin_api_version: int = 1,
1226
+ ) -> None:
1216
1227
  if distname:
1217
1228
  try:
1218
1229
  meta = importlib.metadata.metadata(distname)
@@ -1233,7 +1244,7 @@ class BaseQtPluginDialog(QDialog):
1233
1244
  pypi_versions=[],
1234
1245
  conda_versions=[],
1235
1246
  metadata=self.PACKAGE_METADATA_CLASS(
1236
- metadata_version="1.0",
1247
+ metadata_version='1.0',
1237
1248
  name=norm_name,
1238
1249
  version=meta.get('version', ''),
1239
1250
  summary=meta.get('summary', ''),
@@ -1247,7 +1258,7 @@ class BaseQtPluginDialog(QDialog):
1247
1258
  plugin_api_version=plugin_api_version,
1248
1259
  )
1249
1260
 
1250
- def _add_to_available(self, pkg_name):
1261
+ def _add_to_available(self, pkg_name: str) -> None:
1251
1262
  self._add_items_timer.stop()
1252
1263
  if self._plugin_queue is not None:
1253
1264
  self._plugin_queue.insert(0, self._plugin_data_map[pkg_name])
@@ -1349,10 +1360,10 @@ class BaseQtPluginDialog(QDialog):
1349
1360
  """
1350
1361
  raise NotImplementedError
1351
1362
 
1352
- def _is_main_app_conda_package(self):
1363
+ def _is_main_app_conda_package(self) -> bool:
1353
1364
  return is_conda_package(self.BASE_PACKAGE_NAME)
1354
1365
 
1355
- def _setup_ui(self):
1366
+ def _setup_ui(self) -> None:
1356
1367
  """Defines the layout for the PluginDialog."""
1357
1368
  self.resize(900, 600)
1358
1369
  vlay_1 = QVBoxLayout(self)
@@ -1366,15 +1377,15 @@ class BaseQtPluginDialog(QDialog):
1366
1377
  installed = QWidget(self.v_splitter)
1367
1378
  lay = QVBoxLayout(installed)
1368
1379
  lay.setContentsMargins(0, 2, 0, 2)
1369
- self.installed_label = QLabel(self._trans("Installed Plugins"))
1380
+ self.installed_label = QLabel(self._trans('Installed Plugins'))
1370
1381
  self.packages_search = QLineEdit()
1371
1382
  self.packages_search.setPlaceholderText(
1372
- self._trans("Type here to start searching for plugins...")
1383
+ self._trans('Type here to start searching for plugins...')
1373
1384
  )
1374
1385
  self.packages_search.setToolTip(
1375
1386
  self._trans(
1376
- "The search text will filter currently installed plugins "
1377
- "while also being used to search for plugins on the {package_name} hub",
1387
+ 'The search text will filter currently installed plugins '
1388
+ 'while also being used to search for plugins on the {package_name} hub',
1378
1389
  package_name=self.BASE_PACKAGE_NAME,
1379
1390
  )
1380
1391
  )
@@ -1383,19 +1394,19 @@ class BaseQtPluginDialog(QDialog):
1383
1394
  self.packages_search.textChanged.connect(self.search)
1384
1395
 
1385
1396
  self.import_button = QPushButton(self._trans('Import'), self)
1386
- self.import_button.setObjectName("import_button")
1397
+ self.import_button.setObjectName('import_button')
1387
1398
  self.import_button.setToolTip(self._trans('Import plugins from file'))
1388
1399
  self.import_button.clicked.connect(self._import_plugins)
1389
1400
 
1390
1401
  self.export_button = QPushButton(self._trans('Export'), self)
1391
- self.export_button.setObjectName("export_button")
1402
+ self.export_button.setObjectName('export_button')
1392
1403
  self.export_button.setToolTip(
1393
1404
  self._trans('Export installed plugins list')
1394
1405
  )
1395
1406
  self.export_button.clicked.connect(self._export_plugins)
1396
1407
 
1397
1408
  self.refresh_button = QPushButton(self._trans('Refresh'), self)
1398
- self.refresh_button.setObjectName("refresh_button")
1409
+ self.refresh_button.setObjectName('refresh_button')
1399
1410
  self.refresh_button.setToolTip(
1400
1411
  self._trans(
1401
1412
  'This will clear and refresh the available and installed plugins lists.'
@@ -1422,31 +1433,39 @@ class BaseQtPluginDialog(QDialog):
1422
1433
  uninstalled = QWidget(self.v_splitter)
1423
1434
  lay = QVBoxLayout(uninstalled)
1424
1435
  lay.setContentsMargins(0, 2, 0, 2)
1425
- self.avail_label = QLabel(self._trans("Available Plugins"))
1436
+ self.avail_label = QLabel(self._trans('Available Plugins'))
1426
1437
  mid_layout = QHBoxLayout()
1427
1438
  mid_layout.addWidget(self.avail_label)
1428
1439
  mid_layout.addStretch()
1429
1440
  lay.addLayout(mid_layout)
1441
+ self.available_widget = QStackedWidget()
1430
1442
  self.available_list = self.PLUGIN_LIST_CLASS(
1431
1443
  uninstalled, self.installer, self.BASE_PACKAGE_NAME
1432
1444
  )
1433
- lay.addWidget(self.available_list)
1445
+ self.available_message = QLabel(
1446
+ self._trans('Use the search box above to find plugins.')
1447
+ )
1448
+ self.available_message.setObjectName('available_message')
1449
+ self.available_message.setAlignment(Qt.AlignCenter)
1450
+ self.available_widget.addWidget(self.available_list)
1451
+ self.available_widget.addWidget(self.available_message)
1452
+ lay.addWidget(self.available_widget)
1434
1453
 
1435
1454
  self.stdout_text = QTextEdit(self.v_splitter)
1436
1455
  self.stdout_text.setReadOnly(True)
1437
- self.stdout_text.setObjectName("plugin_manager_process_status")
1456
+ self.stdout_text.setObjectName('plugin_manager_process_status')
1438
1457
  self.stdout_text.hide()
1439
1458
 
1440
1459
  buttonBox = QHBoxLayout()
1441
- self.working_indicator = QLabel(self._trans("loading ..."), self)
1460
+ self.working_indicator = QLabel(self._trans('loading ...'), self)
1442
1461
  sp = self.working_indicator.sizePolicy()
1443
1462
  sp.setRetainSizeWhenHidden(True)
1444
1463
  self.working_indicator.setSizePolicy(sp)
1445
1464
  self.process_error_indicator = QLabel(self)
1446
- self.process_error_indicator.setObjectName("error_label")
1465
+ self.process_error_indicator.setObjectName('error_label')
1447
1466
  self.process_error_indicator.hide()
1448
1467
  self.process_success_indicator = QLabel(self)
1449
- self.process_success_indicator.setObjectName("success_label")
1468
+ self.process_success_indicator.setObjectName('success_label')
1450
1469
  self.process_success_indicator.hide()
1451
1470
  mov = self._loading_gif()
1452
1471
  self.working_indicator.setMovie(mov)
@@ -1460,13 +1479,13 @@ class BaseQtPluginDialog(QDialog):
1460
1479
  self.direct_entry_btn = QToolButton(self)
1461
1480
  self.direct_entry_btn.setVisible(visibility_direct_entry)
1462
1481
  self.direct_entry_btn.clicked.connect(self._install_packages)
1463
- self.direct_entry_btn.setText(self._trans("Install"))
1482
+ self.direct_entry_btn.setText(self._trans('Install'))
1464
1483
 
1465
1484
  self._action_conda = QAction(self._trans('Conda'), self)
1466
1485
  self._action_conda.setCheckable(True)
1467
1486
  self._action_conda.triggered.connect(self._update_direct_entry_text)
1468
1487
 
1469
- self._action_pypi = QAction(self._trans('pip'), self)
1488
+ self._action_pypi = QAction(self._trans('PyPI'), self)
1470
1489
  self._action_pypi.setCheckable(True)
1471
1490
  self._action_pypi.triggered.connect(self._update_direct_entry_text)
1472
1491
 
@@ -1484,31 +1503,28 @@ class BaseQtPluginDialog(QDialog):
1484
1503
  self._action_conda.setChecked(True)
1485
1504
  self.direct_entry_btn.setMenu(self._menu)
1486
1505
 
1487
- self.show_status_btn = QPushButton(self._trans("Show Status"), self)
1488
- self.show_status_btn.setFixedWidth(100)
1506
+ self.show_status_btn = QPushButton(self._trans('Show Status'), self)
1489
1507
 
1490
1508
  self.cancel_all_btn = QPushButton(
1491
- self._trans("cancel all actions"), self
1509
+ self._trans('cancel all actions'), self
1492
1510
  )
1493
- self.cancel_all_btn.setObjectName("remove_button")
1511
+ self.cancel_all_btn.setObjectName('remove_button')
1494
1512
  self.cancel_all_btn.setVisible(False)
1495
1513
  self.cancel_all_btn.clicked.connect(self.installer.cancel_all)
1496
1514
 
1497
- self.close_btn = QPushButton(self._trans("Close"), self)
1515
+ self.close_btn = QPushButton(self._trans('Close'), self)
1498
1516
  self.close_btn.clicked.connect(self.accept)
1499
- self.close_btn.setObjectName("close_button")
1517
+ self.close_btn.setObjectName('close_button')
1500
1518
 
1501
- buttonBox.addWidget(self.show_status_btn)
1502
- buttonBox.addWidget(self.working_indicator)
1503
1519
  buttonBox.addWidget(self.direct_entry_edit)
1504
1520
  buttonBox.addWidget(self.direct_entry_btn)
1505
1521
  if not visibility_direct_entry:
1506
1522
  buttonBox.addStretch()
1507
1523
  buttonBox.addWidget(self.process_success_indicator)
1508
1524
  buttonBox.addWidget(self.process_error_indicator)
1509
- buttonBox.addSpacing(20)
1525
+ buttonBox.addWidget(self.show_status_btn)
1510
1526
  buttonBox.addWidget(self.cancel_all_btn)
1511
- buttonBox.addSpacing(20)
1527
+ buttonBox.addWidget(self.working_indicator)
1512
1528
  buttonBox.addWidget(self.close_btn)
1513
1529
  buttonBox.setContentsMargins(0, 0, 4, 0)
1514
1530
  vlay_1.addLayout(buttonBox)
@@ -1517,25 +1533,25 @@ class BaseQtPluginDialog(QDialog):
1517
1533
  self.show_status_btn.setChecked(False)
1518
1534
  self.show_status_btn.toggled.connect(self.toggle_status)
1519
1535
 
1520
- self.v_splitter.setStretchFactor(1, 2)
1536
+ self.v_splitter.setStretchFactor(0, 2)
1521
1537
  self.h_splitter.setStretchFactor(0, 2)
1522
1538
 
1523
1539
  self.packages_search.setFocus()
1524
1540
  self._update_direct_entry_text()
1525
1541
 
1526
- def _update_direct_entry_text(self):
1542
+ def _update_direct_entry_text(self) -> None:
1527
1543
  tool = (
1528
1544
  str(InstallerTools.CONDA)
1529
1545
  if self._action_conda.isChecked()
1530
- else str(InstallerTools.PIP)
1546
+ else str(InstallerTools.PYPI)
1531
1547
  )
1532
1548
  self.direct_entry_edit.setPlaceholderText(
1533
1549
  self._trans(
1534
- "install with '{tool}' by name/url, or drop file...", tool=tool
1550
+ "install from '{tool}' by name/url, or drop file...", tool=tool
1535
1551
  )
1536
1552
  )
1537
1553
 
1538
- def _update_plugin_count(self):
1554
+ def _update_plugin_count(self) -> None:
1539
1555
  """Update count labels for both installed and available plugin lists.
1540
1556
  Displays also amount of visible plugins out of total when filtering.
1541
1557
  """
@@ -1544,14 +1560,14 @@ class BaseQtPluginDialog(QDialog):
1544
1560
  if installed_count == installed_count_visible:
1545
1561
  self.installed_label.setText(
1546
1562
  self._trans(
1547
- "Installed Plugins ({amount})",
1563
+ 'Installed Plugins ({amount})',
1548
1564
  amount=installed_count,
1549
1565
  )
1550
1566
  )
1551
1567
  else:
1552
1568
  self.installed_label.setText(
1553
1569
  self._trans(
1554
- "Installed Plugins ({count}/{amount})",
1570
+ 'Installed Plugins ({count}/{amount})',
1555
1571
  count=installed_count_visible,
1556
1572
  amount=installed_count,
1557
1573
  )
@@ -1563,14 +1579,14 @@ class BaseQtPluginDialog(QDialog):
1563
1579
  if self._plugins_found == 0:
1564
1580
  self.avail_label.setText(
1565
1581
  self._trans(
1566
- "{amount} plugins available on the napari hub",
1582
+ '{amount} plugins available on the napari hub',
1567
1583
  amount=available_count,
1568
1584
  )
1569
1585
  )
1570
1586
  elif self._plugins_found > self.MAX_PLUGIN_SEARCH_ITEMS:
1571
1587
  self.avail_label.setText(
1572
1588
  self._trans(
1573
- "Found {found} out of {amount} plugins on the napari hub. Displaying the first {max_count}...",
1589
+ 'Found {found} out of {amount} plugins on the napari hub. Displaying the first {max_count}...',
1574
1590
  found=self._plugins_found,
1575
1591
  amount=available_count,
1576
1592
  max_count=self.MAX_PLUGIN_SEARCH_ITEMS,
@@ -1579,7 +1595,7 @@ class BaseQtPluginDialog(QDialog):
1579
1595
  else:
1580
1596
  self.avail_label.setText(
1581
1597
  self._trans(
1582
- "Found {found} out of {amount} plugins on the napari hub",
1598
+ 'Found {found} out of {amount} plugins on the napari hub',
1583
1599
  found=self._plugins_found,
1584
1600
  amount=available_count,
1585
1601
  )
@@ -1588,7 +1604,7 @@ class BaseQtPluginDialog(QDialog):
1588
1604
  def _install_packages(
1589
1605
  self,
1590
1606
  packages: Sequence[str] = (),
1591
- ):
1607
+ ) -> None:
1592
1608
  if not packages:
1593
1609
  _packages = self.direct_entry_edit.text()
1594
1610
  packages = (
@@ -1600,11 +1616,11 @@ class BaseQtPluginDialog(QDialog):
1600
1616
  tool = (
1601
1617
  InstallerTools.CONDA
1602
1618
  if self._action_conda.isChecked()
1603
- else InstallerTools.PIP
1619
+ else InstallerTools.PYPI
1604
1620
  )
1605
1621
  self.installer.install(tool, packages)
1606
1622
 
1607
- def _tag_outdated_plugins(self):
1623
+ def _tag_outdated_plugins(self) -> None:
1608
1624
  """Tag installed plugins that might be outdated."""
1609
1625
  for pkg_name in self.installed_list.packages():
1610
1626
  _data = self._plugin_data_map.get(pkg_name)
@@ -1614,7 +1630,7 @@ class BaseQtPluginDialog(QDialog):
1614
1630
  metadata, is_available_in_conda
1615
1631
  )
1616
1632
 
1617
- def _add_items(self):
1633
+ def _add_items(self) -> None:
1618
1634
  """
1619
1635
  Add items to the lists by `batch_size` using a timer to add a pause
1620
1636
  and prevent freezing the UI.
@@ -1668,7 +1684,9 @@ class BaseQtPluginDialog(QDialog):
1668
1684
 
1669
1685
  self._update_plugin_count()
1670
1686
 
1671
- def _handle_yield(self, data: tuple[PackageMetadataProtocol, bool, dict]):
1687
+ def _handle_yield(
1688
+ self, data: tuple[PackageMetadataProtocol, bool, dict]
1689
+ ) -> None:
1672
1690
  """Output from a worker process.
1673
1691
 
1674
1692
  Includes information about the plugin, including available versions on conda and pypi.
@@ -1678,7 +1696,7 @@ class BaseQtPluginDialog(QDialog):
1678
1696
  """
1679
1697
  self._plugin_data.append(data)
1680
1698
  self._filter_texts = [
1681
- f"{i[0].name} {i[-1].get('display_name', '')} {i[0].summary}".lower()
1699
+ f'{i[0].name} {i[-1].get("display_name", "")} {i[0].summary}'.lower()
1682
1700
  for i in self._plugin_data
1683
1701
  ]
1684
1702
  metadata, _, _ = data
@@ -1686,7 +1704,7 @@ class BaseQtPluginDialog(QDialog):
1686
1704
  self.available_list.set_data(self._plugin_data)
1687
1705
  self._update_plugin_count()
1688
1706
 
1689
- def _search_in_available(self, text):
1707
+ def _search_in_available(self, text: str) -> list[int]:
1690
1708
  idxs = []
1691
1709
  for idx, item in enumerate(self._filter_texts):
1692
1710
  if text.lower().strip() in item:
@@ -1695,16 +1713,16 @@ class BaseQtPluginDialog(QDialog):
1695
1713
 
1696
1714
  return idxs
1697
1715
 
1698
- def _refresh_and_clear_cache(self):
1716
+ def _refresh_and_clear_cache(self) -> None:
1699
1717
  self.refresh(clear_cache=True)
1700
1718
 
1701
- def _import_plugins(self):
1702
- fpath, _ = getopenfilename(filters="Text files (*.txt)")
1719
+ def _import_plugins(self) -> None:
1720
+ fpath, _ = getopenfilename(filters='Text files (*.txt)')
1703
1721
  if fpath:
1704
1722
  self.import_plugins(fpath)
1705
1723
 
1706
- def _export_plugins(self):
1707
- fpath, _ = getsavefilename(filters="Text files (*.txt)")
1724
+ def _export_plugins(self) -> None:
1725
+ fpath, _ = getsavefilename(filters='Text files (*.txt)')
1708
1726
  if fpath:
1709
1727
  self.export_plugins(fpath)
1710
1728
 
@@ -1712,7 +1730,7 @@ class BaseQtPluginDialog(QDialog):
1712
1730
 
1713
1731
  # region - Qt overrides
1714
1732
  # ------------------------------------------------------------------------
1715
- def closeEvent(self, event):
1733
+ def closeEvent(self, event: QCloseEvent) -> None:
1716
1734
  if self._parent is not None:
1717
1735
  plugin_dialog = getattr(self._parent, '_plugin_dialog', self)
1718
1736
  if self != plugin_dialog:
@@ -1723,10 +1741,10 @@ class BaseQtPluginDialog(QDialog):
1723
1741
  else:
1724
1742
  super().closeEvent(event)
1725
1743
 
1726
- def dragEnterEvent(self, event):
1744
+ def dragEnterEvent(self, event: QEnterEvent) -> None:
1727
1745
  event.accept()
1728
1746
 
1729
- def dropEvent(self, event):
1747
+ def dropEvent(self, event: QDropEvent) -> None:
1730
1748
  md = event.mimeData()
1731
1749
  if md.hasUrls():
1732
1750
  files = [url.toLocalFile() for url in md.urls()]
@@ -1735,24 +1753,24 @@ class BaseQtPluginDialog(QDialog):
1735
1753
 
1736
1754
  return super().dropEvent(event)
1737
1755
 
1738
- def exec_(self):
1756
+ def exec_(self) -> None:
1739
1757
  plugin_dialog = getattr(self._parent, '_plugin_dialog', self)
1740
1758
  if plugin_dialog != self:
1741
1759
  self.close()
1742
1760
 
1743
1761
  plugin_dialog.setModal(True)
1744
1762
  plugin_dialog.show()
1745
- plugin_dialog._installed_on_show = set(plugin_dialog.already_installed)
1746
1763
 
1747
1764
  if self._first_open:
1748
1765
  self._update_theme(None)
1749
1766
  self._first_open = False
1750
1767
 
1751
- def hideEvent(self, event):
1752
- if (
1753
- hasattr(self, '_installed_on_show')
1754
- and self._installed_on_show != self.already_installed
1755
- ):
1768
+ def hideEvent(self, event: QHideEvent) -> None:
1769
+ if len(self.modified_set):
1770
+ # At least one plugin was installed, uninstalled or updated so
1771
+ # clear modified packages (to show warning only once) and
1772
+ # show restart warning
1773
+ self.modified_set = set()
1756
1774
  RestartWarningDialog(self).exec_()
1757
1775
  self.packages_search.clear()
1758
1776
  self.toggle_status(False)
@@ -1771,11 +1789,12 @@ class BaseQtPluginDialog(QDialog):
1771
1789
 
1772
1790
  if len(text.strip()) == 0:
1773
1791
  self.installed_list.filter('')
1774
- self.available_list.hideAll()
1792
+ self.available_widget.setCurrentWidget(self.available_message)
1775
1793
  self._plugin_queue = None
1776
1794
  self._add_items_timer.stop()
1777
1795
  self._plugins_found = 0
1778
1796
  else:
1797
+ self.available_widget.setCurrentWidget(self.available_list)
1779
1798
  items = [
1780
1799
  self._plugin_data[idx]
1781
1800
  for idx in self._search_in_available(text)
@@ -1796,7 +1815,7 @@ class BaseQtPluginDialog(QDialog):
1796
1815
 
1797
1816
  self._update_plugin_count()
1798
1817
 
1799
- def refresh(self, clear_cache: bool = False):
1818
+ def refresh(self, clear_cache: bool = False) -> None:
1800
1819
  self.refresh_button.setDisabled(True)
1801
1820
 
1802
1821
  if self.worker is not None:
@@ -1820,16 +1839,16 @@ class BaseQtPluginDialog(QDialog):
1820
1839
 
1821
1840
  self._refresh_timer.start()
1822
1841
 
1823
- def toggle_status(self, show=None):
1842
+ def toggle_status(self, show: bool | None = None) -> None:
1824
1843
  show = not self.stdout_text.isVisible() if show is None else show
1825
1844
  if show:
1826
- self.show_status_btn.setText(self._trans("Hide Status"))
1845
+ self.show_status_btn.setText(self._trans('Hide Status'))
1827
1846
  self.stdout_text.show()
1828
1847
  else:
1829
- self.show_status_btn.setText(self._trans("Show Status"))
1848
+ self.show_status_btn.setText(self._trans('Show Status'))
1830
1849
  self.stdout_text.hide()
1831
1850
 
1832
- def set_prefix(self, prefix):
1851
+ def set_prefix(self, prefix) -> None:
1833
1852
  self._prefix = prefix
1834
1853
  self.installer._prefix = prefix
1835
1854
  for idx in range(self.available_list.count()):
@@ -1849,7 +1868,7 @@ class BaseQtPluginDialog(QDialog):
1849
1868
  if item:
1850
1869
  name = item.widget.name
1851
1870
  version = item.widget._version # Make public attr?
1852
- plugins.append(f"{name}=={version}\n")
1871
+ plugins.append(f'{name}=={version}\n')
1853
1872
 
1854
1873
  with open(fpath, 'w') as f:
1855
1874
  f.writelines(plugins)
@@ -1861,7 +1880,7 @@ class BaseQtPluginDialog(QDialog):
1861
1880
  with open(fpath) as f:
1862
1881
  plugins = f.read().split('\n')
1863
1882
 
1864
- print(plugins)
1883
+ log.info(plugins)
1865
1884
 
1866
1885
  plugins = [p for p in plugins if p]
1867
1886
  self._install_packages(plugins)