Added functionality to reinstall single or all pipx managed packages.

Thu, 27 Jun 2024 17:50:51 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 27 Jun 2024 17:50:51 +0200
changeset 12
a09f763d5e1f
parent 11
6af0704c8175
child 13
e0359a1339fe

Added functionality to reinstall single or all pipx managed packages.

PipxInterface/Pipx.py file | annotate | diff | comparison | revisions
PipxInterface/PipxPackagesInputDialog.py file | annotate | diff | comparison | revisions
PipxInterface/PipxReinstallDialog.py file | annotate | diff | comparison | revisions
PipxInterface/PipxReinstallDialog.ui file | annotate | diff | comparison | revisions
PipxInterface/PipxWidget.py file | annotate | diff | comparison | revisions
PipxInterface/Ui_PipxReinstallDialog.py file | annotate | diff | comparison | revisions
PluginPipxInterface.epj file | annotate | diff | comparison | revisions
diff -r 6af0704c8175 -r a09f763d5e1f PipxInterface/Pipx.py
--- a/PipxInterface/Pipx.py	Thu Jun 27 16:20:56 2024 +0200
+++ b/PipxInterface/Pipx.py	Thu Jun 27 17:50:51 2024 +0200
@@ -206,7 +206,7 @@
         systemSitePackages=False
     ):
         """
-        Public method 
+        Public method to install a list of packages with the given options.
 
         @param packages list of packages to install
         @type list of str
@@ -229,8 +229,8 @@
             args += ["--index-url", indexUrl]
         if interpreterVersion:
             args += ["--python", interpreterVersion]
-        if fetchMissingInterpreter:
-            args.append("--fetch-missing-python")
+            if fetchMissingInterpreter:
+                args.append("--fetch-missing-python")
         if forceVenvModification:
             args.append("--force")
         if systemSitePackages:
@@ -250,7 +250,8 @@
         systemSitePackages=False
     ):
         """
-        Public method 
+        Public method to install all packages define by a given spec metadata file
+        with given options.
 
         @param specFile path of the spec metadata file
         @type str
@@ -273,8 +274,8 @@
             args += ["--index-url", indexUrl]
         if interpreterVersion:
             args += ["--python", interpreterVersion]
-        if fetchMissingInterpreter:
-            args.append("--fetch-missing-python")
+            if fetchMissingInterpreter:
+                args.append("--fetch-missing-python")
         if forceVenvModification:
             args.append("--force")
         if systemSitePackages:
@@ -306,6 +307,68 @@
         else:
             return False, output
 
+    def reinstallPackage(
+        self,
+        package,
+        interpreterVersion="",
+        fetchMissingInterpreter=False,
+    ):
+        """
+        Public method to reinstall the given package with given options
+
+        @param package name of the package to reinstall
+        @type str
+        @param interpreterVersion version of the Python interpreter (defaults to "")
+        @type str (optional)
+        @param fetchMissingInterpreter flag indicating to fetch a standalone Python
+            build from GitHub if the specified Python version is not found locally
+            on the system (defaults to False)
+        @type bool (optional)
+        """
+        args = ["reinstall"]
+        if interpreterVersion:
+            args += ["--python", interpreterVersion]
+            if fetchMissingInterpreter:
+                args.append("--fetch-missing-python")
+        args.append(package)
+        dia = PipxExecDialog(self.tr("Re-Install Package"))
+        res = dia.startProcess(self.__getPipxExecutable(), args)
+        if res:
+            dia.exec()
+
+    def reinstallAllPackages(
+        self,
+        interpreterVersion="",
+        fetchMissingInterpreter=False,
+        skipPackages=None,
+    ):
+        """
+        Public method to reinstall all packages with given options
+
+        @param package name of the package to reinstall
+        @type str
+        @param interpreterVersion version of the Python interpreter (defaults to "")
+        @type str (optional)
+        @param fetchMissingInterpreter flag indicating to fetch a standalone Python
+            build from GitHub if the specified Python version is not found locally
+            on the system (defaults to False)
+        @type bool (optional)
+        @param skipPackages list of packages to be skipped by the 'reinstall-all'
+            command (defaults to None)
+        @type list of str (optional)
+        """
+        args = ["reinstall-all"]
+        if interpreterVersion:
+            args += ["--python", interpreterVersion]
+            if fetchMissingInterpreter:
+                args.append("--fetch-missing-python")
+        if skipPackages:
+            args += ["--skip"] + skipPackages
+        dia = PipxExecDialog(self.tr("Re-Install All Packages"))
+        res = dia.startProcess(self.__getPipxExecutable(), args)
+        if res:
+            dia.exec()
+
     def uninstallPackage(self, package):
         """
         Public method to uninstall the given package.
diff -r 6af0704c8175 -r a09f763d5e1f PipxInterface/PipxPackagesInputDialog.py
--- a/PipxInterface/PipxPackagesInputDialog.py	Thu Jun 27 16:20:56 2024 +0200
+++ b/PipxInterface/PipxPackagesInputDialog.py	Thu Jun 27 17:50:51 2024 +0200
@@ -60,10 +60,8 @@
             indicating to give access to the system site-packages directory.
         @rtype tuple of (list of str, str, bool, bool, bool)
         """
-        packages = [p.strip() for p in self.packagesEdit.text().split()]
-
         return (
-            packages,
+            [p.strip() for p in self.packagesEdit.text().split()],
             self.interpreterVersionEdit.text().strip(),
             self.fetchMissingCheckBox.isChecked(),
             self.forceCheckBox.isChecked(),
diff -r 6af0704c8175 -r a09f763d5e1f PipxInterface/PipxReinstallDialog.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PipxInterface/PipxReinstallDialog.py	Thu Jun 27 17:50:51 2024 +0200
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to enter the desired Python interpreter version.
+"""
+
+from PyQt6.QtWidgets import QDialog
+
+from .Ui_PipxReinstallDialog import Ui_PipxReinstallDialog
+
+
+class PipxReinstallDialog(QDialog, Ui_PipxReinstallDialog):
+    """
+    Class implementing a dialog to enter the desired Python interpreter version.
+    """
+
+    def __init__(self, reinstallAll=False, parent=None):
+        """
+        Constructor
+
+        @param reinstallAll flag indicating to get the parameters for a 'reinstall-all'
+            action
+        @type bool
+        @param parent reference to the parent widget (defaults to None)
+        @type QWidget (optional)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+
+        self.skipGroupBox.setVisible(reinstallAll)
+
+        self.fetchMissingCheckBox.setChecked(True)
+
+        msh = self.minimumSizeHint()
+        self.resize(max(self.width(), msh.width()), msh.height())
+
+    def getData(self):
+        """
+        Public method to get the entered data.
+
+        @return tuple containing the desired Python version, a flag indicating
+            to fetch a standalone Python build from GitHub if the specified Python
+            version is not found locally on the system and a list of packages to skip
+            in case of a 'reinstall-all'
+        @rtype tuple of (str, bool, list of str)
+        """
+        return (
+            self.interpreterVersionEdit.text(),
+            self.fetchMissingCheckBox.isChecked(),
+            [p.strip() for p in self.packagesEdit.text().split()],
+        )
diff -r 6af0704c8175 -r a09f763d5e1f PipxInterface/PipxReinstallDialog.ui
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PipxInterface/PipxReinstallDialog.ui	Thu Jun 27 17:50:51 2024 +0200
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PipxReinstallDialog</class>
+ <widget class="QDialog" name="PipxReinstallDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>268</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Package Re-Installation</string>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2">
+   <item>
+    <widget class="QGroupBox" name="skipGroupBox">
+     <property name="title">
+      <string>Skip Packages</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <widget class="QLabel" name="label_3">
+        <property name="text">
+         <string>Package Specifications (separated by whitespace):</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="packagesEdit"/>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="interpreterGroupBox">
+     <property name="title">
+      <string>Standalone Python Interpreter</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout">
+      <item row="0" column="0" colspan="2">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>&lt;b&gt;Note:&lt;/b&gt; Leave this entry empty to use the default Python interpreter.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_4">
+        <property name="text">
+         <string>Version:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="QLineEdit" name="interpreterVersionEdit">
+        <property name="toolTip">
+         <string>Enter the version number of the Python interpreter to be used.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="0" colspan="2">
+       <widget class="QCheckBox" name="fetchMissingCheckBox">
+        <property name="toolTip">
+         <string>Select to fetch a standalone Python build from GitHub if the specified Python version is not found locally on the system.</string>
+        </property>
+        <property name="text">
+         <string>Fetch missing Python interpreter</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>packagesEdit</tabstop>
+  <tabstop>interpreterVersionEdit</tabstop>
+  <tabstop>fetchMissingCheckBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>PipxReinstallDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>PipxReinstallDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff -r 6af0704c8175 -r a09f763d5e1f PipxInterface/PipxWidget.py
--- a/PipxInterface/PipxWidget.py	Thu Jun 27 16:20:56 2024 +0200
+++ b/PipxInterface/PipxWidget.py	Thu Jun 27 17:50:51 2024 +0200
@@ -107,7 +107,7 @@
         )
         self.__installSubmenu.addSeparator()
         self.__reinstallPackagesAct = self.__installSubmenu.addAction(
-            self.tr("Re-Install Selected Packages"), self.__reinstallPackages
+            self.tr("Re-Install Selected Package"), self.__reinstallPackage
         )
         self.__reinstallAllPackagesAct = self.__installSubmenu.addAction(
             self.tr("Re-Install All Packages"), self.__reinstallAllPackages
@@ -261,20 +261,53 @@
                 )
 
     @pyqtSlot()
-    def __reinstallPackages(self):
+    def __reinstallPackage(self):
+        """
+        Private slot to force a re-installation of the selected package.
         """
-        Private slot to force a re-installation of the selected packages.
-        """
-        # TODO: not implemented yet
-        pass
+        from .PipxReinstallDialog import PipxReinstallDialog
+
+        package = self.__selectedPackages()[0]
+        yes = EricMessageBox.yesNo(
+            self,
+            self.tr("Re-Install Package"),
+            self.tr(
+                "<p>Shall the package <b>{0}</b> really be reinstalled?</p>"
+            ).format(package),
+        )
+        if yes:
+            dlg = PipxReinstallDialog(reinstallAll=False, parent=self)
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                pyVersion, fetchMissing, _ = dlg.getData()
+                self.__pipx.reinstallPackage(
+                    package,
+                    interpreterVersion=pyVersion,
+                    fetchMissingInterpreter=fetchMissing,
+                )
+            self.on_refreshButton_clicked()
 
     @pyqtSlot()
     def __reinstallAllPackages(self):
         """
         Private slot to force a re-installation of all packages.
         """
-        # TODO: not implemented yet
-        pass
+        from .PipxReinstallDialog import PipxReinstallDialog
+
+        yes = EricMessageBox.yesNo(
+            self,
+            self.tr("Re-Install All Packages"),
+            self.tr("""Do you really want to reinstall all packages?"""),
+        )
+        if yes:
+            dlg = PipxReinstallDialog(reinstallAll=True, parent=self)
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                pyVersion, fetchMissing, skipList = dlg.getData()
+                self.__pipx.reinstallAllPackages(
+                    interpreterVersion=pyVersion,
+                    fetchMissingInterpreter=fetchMissing,
+                    skipPackages=skipList,
+                )
+            self.on_refreshButton_clicked()
 
     @pyqtSlot()
     def __upgradePackage(self):
diff -r 6af0704c8175 -r a09f763d5e1f PipxInterface/Ui_PipxReinstallDialog.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PipxInterface/Ui_PipxReinstallDialog.py	Thu Jun 27 17:50:51 2024 +0200
@@ -0,0 +1,70 @@
+# Form implementation generated from reading ui file 'PipxInterface/PipxReinstallDialog.ui'
+#
+# Created by: PyQt6 UI code generator 6.7.0
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic6 is
+# run again.  Do not edit this file unless you know what you are doing.
+
+
+from PyQt6 import QtCore, QtGui, QtWidgets
+
+
+class Ui_PipxReinstallDialog(object):
+    def setupUi(self, PipxReinstallDialog):
+        PipxReinstallDialog.setObjectName("PipxReinstallDialog")
+        PipxReinstallDialog.resize(600, 268)
+        PipxReinstallDialog.setSizeGripEnabled(True)
+        self.verticalLayout_2 = QtWidgets.QVBoxLayout(PipxReinstallDialog)
+        self.verticalLayout_2.setObjectName("verticalLayout_2")
+        self.skipGroupBox = QtWidgets.QGroupBox(parent=PipxReinstallDialog)
+        self.skipGroupBox.setObjectName("skipGroupBox")
+        self.verticalLayout = QtWidgets.QVBoxLayout(self.skipGroupBox)
+        self.verticalLayout.setObjectName("verticalLayout")
+        self.label_3 = QtWidgets.QLabel(parent=self.skipGroupBox)
+        self.label_3.setObjectName("label_3")
+        self.verticalLayout.addWidget(self.label_3)
+        self.packagesEdit = QtWidgets.QLineEdit(parent=self.skipGroupBox)
+        self.packagesEdit.setObjectName("packagesEdit")
+        self.verticalLayout.addWidget(self.packagesEdit)
+        self.verticalLayout_2.addWidget(self.skipGroupBox)
+        self.interpreterGroupBox = QtWidgets.QGroupBox(parent=PipxReinstallDialog)
+        self.interpreterGroupBox.setObjectName("interpreterGroupBox")
+        self.gridLayout = QtWidgets.QGridLayout(self.interpreterGroupBox)
+        self.gridLayout.setObjectName("gridLayout")
+        self.label_2 = QtWidgets.QLabel(parent=self.interpreterGroupBox)
+        self.label_2.setObjectName("label_2")
+        self.gridLayout.addWidget(self.label_2, 0, 0, 1, 2)
+        self.label_4 = QtWidgets.QLabel(parent=self.interpreterGroupBox)
+        self.label_4.setObjectName("label_4")
+        self.gridLayout.addWidget(self.label_4, 1, 0, 1, 1)
+        self.interpreterVersionEdit = QtWidgets.QLineEdit(parent=self.interpreterGroupBox)
+        self.interpreterVersionEdit.setObjectName("interpreterVersionEdit")
+        self.gridLayout.addWidget(self.interpreterVersionEdit, 1, 1, 1, 1)
+        self.fetchMissingCheckBox = QtWidgets.QCheckBox(parent=self.interpreterGroupBox)
+        self.fetchMissingCheckBox.setObjectName("fetchMissingCheckBox")
+        self.gridLayout.addWidget(self.fetchMissingCheckBox, 2, 0, 1, 2)
+        self.verticalLayout_2.addWidget(self.interpreterGroupBox)
+        self.buttonBox = QtWidgets.QDialogButtonBox(parent=PipxReinstallDialog)
+        self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal)
+        self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok)
+        self.buttonBox.setObjectName("buttonBox")
+        self.verticalLayout_2.addWidget(self.buttonBox)
+
+        self.retranslateUi(PipxReinstallDialog)
+        self.buttonBox.accepted.connect(PipxReinstallDialog.accept) # type: ignore
+        self.buttonBox.rejected.connect(PipxReinstallDialog.reject) # type: ignore
+        QtCore.QMetaObject.connectSlotsByName(PipxReinstallDialog)
+        PipxReinstallDialog.setTabOrder(self.packagesEdit, self.interpreterVersionEdit)
+        PipxReinstallDialog.setTabOrder(self.interpreterVersionEdit, self.fetchMissingCheckBox)
+
+    def retranslateUi(self, PipxReinstallDialog):
+        _translate = QtCore.QCoreApplication.translate
+        PipxReinstallDialog.setWindowTitle(_translate("PipxReinstallDialog", "Package Re-Installation"))
+        self.skipGroupBox.setTitle(_translate("PipxReinstallDialog", "Skip Packages"))
+        self.label_3.setText(_translate("PipxReinstallDialog", "Package Specifications (separated by whitespace):"))
+        self.interpreterGroupBox.setTitle(_translate("PipxReinstallDialog", "Standalone Python Interpreter"))
+        self.label_2.setText(_translate("PipxReinstallDialog", "<b>Note:</b> Leave this entry empty to use the default Python interpreter."))
+        self.label_4.setText(_translate("PipxReinstallDialog", "Version:"))
+        self.interpreterVersionEdit.setToolTip(_translate("PipxReinstallDialog", "Enter the version number of the Python interpreter to be used."))
+        self.fetchMissingCheckBox.setToolTip(_translate("PipxReinstallDialog", "Select to fetch a standalone Python build from GitHub if the specified Python version is not found locally on the system."))
+        self.fetchMissingCheckBox.setText(_translate("PipxReinstallDialog", "Fetch missing Python interpreter"))
diff -r 6af0704c8175 -r a09f763d5e1f PluginPipxInterface.epj
--- a/PluginPipxInterface.epj	Thu Jun 27 16:20:56 2024 +0200
+++ b/PluginPipxInterface.epj	Thu Jun 27 17:50:51 2024 +0200
@@ -42,6 +42,7 @@
       "PipxInterface/PipxAppStartDialog.ui",
       "PipxInterface/PipxExecDialog.ui",
       "PipxInterface/PipxPackagesInputDialog.ui",
+      "PipxInterface/PipxReinstallDialog.ui",
       "PipxInterface/PipxSpecInputDialog.ui",
       "PipxInterface/PipxWidget.ui"
     ],
@@ -130,11 +131,13 @@
       "PipxInterface/PipxAppStartDialog.py",
       "PipxInterface/PipxExecDialog.py",
       "PipxInterface/PipxPackagesInputDialog.py",
+      "PipxInterface/PipxReinstallDialog.py",
       "PipxInterface/PipxSpecInputDialog.py",
       "PipxInterface/PipxWidget.py",
       "PipxInterface/Ui_PipxAppStartDialog.py",
       "PipxInterface/Ui_PipxExecDialog.py",
       "PipxInterface/Ui_PipxPackagesInputDialog.py",
+      "PipxInterface/Ui_PipxReinstallDialog.py",
       "PipxInterface/Ui_PipxSpecInputDialog.py",
       "PipxInterface/Ui_PipxWidget.py",
       "PipxInterface/__init__.py",

eric ide

mercurial