PipInterface/PipPackagesWidget.py

branch
pypi
changeset 6792
9dd854f05c83
parent 6785
058d63c537a4
child 6793
cca6a35f3ad2
--- a/PipInterface/PipPackagesWidget.py	Mon Feb 18 19:49:43 2019 +0100
+++ b/PipInterface/PipPackagesWidget.py	Tue Feb 19 19:56:24 2019 +0100
@@ -11,7 +11,10 @@
 
 from PyQt5.QtCore import pyqtSlot, Qt
 from PyQt5.QtGui import QCursor
-from PyQt5.QtWidgets import QWidget, QToolButton, QApplication
+from PyQt5.QtWidgets import QWidget, QToolButton, QApplication, QHeaderView, \
+    QTreeWidgetItem
+
+from E5Gui.E5Application import e5App
 
 from .Ui_PipPackagesWidget import Ui_PipPackagesWidget
 
@@ -22,8 +25,13 @@
 
 class PipPackagesWidget(QWidget, Ui_PipPackagesWidget):
     """
-    Class documentation goes here.
+    Class implementing the pip packages management widget.
     """
+    ShowProcessGeneralMode = 0
+    ShowProcessClassifiersMode = 1
+    ShowProcessEntryPointsMode = 2
+    ShowProcessFilesListMode = 3
+    
     def __init__(self, parent=None):
         """
         Constructor
@@ -48,23 +56,44 @@
         
         self.__pip = Pip(self)
         
+        self.packagesList.header().setSortIndicator(0, Qt.AscendingOrder)
+        
+        self.__infoLabels = {
+            "name": self.tr("Name:"),
+            "version": self.tr("Version:"),
+            "location": self.tr("Location:"),
+            "requires": self.tr("Requires:"),
+            "summary": self.tr("Summary:"),
+            "home-page": self.tr("Homepage:"),
+            "author": self.tr("Author:"),
+            "author-email": self.tr("Author Email:"),
+            "license": self.tr("License:"),
+            "metadata-version": self.tr("Metadata Version:"),
+            "installer": self.tr("Installer:"),
+            "classifiers": self.tr("Classifiers:"),
+            "entry-points": self.tr("Entry Points:"),
+            "files": self.tr("Files:"),
+        }
+        self.infoWidget.setHeaderLabels(["Key", "Value"])
+        
+        venvManager = e5App().getObject("VirtualEnvManager")
+        venvManager.virtualEnvironmentAdded.connect(
+            self.on_refreshButton_clicked)
+        venvManager.virtualEnvironmentRemoved.connect(
+            self.on_refreshButton_clicked)
+        
+        project = e5App().getObject("Project")
+        project.projectOpened.connect(
+            self.on_refreshButton_clicked)
+        project.projectClosed.connect(
+            self.on_refreshButton_clicked)
+        
         self.__initPipMenu()
         self.__populateEnvironments()
         self.__updateActionButtons()
         
         self.statusLabel.hide()
         self.searchWidget.hide()
-        
-    def __initPipMenu(self):
-        """
-        Private method to create the super menu and attach it to the super
-        menu button.
-        """
-        self.__pip.initActions()
-        
-        self.__pipMenu = self.__pip.initMenu()
-        
-        self.pipMenuButton.setMenu(self.__pipMenu)
     
     def __populateEnvironments(self):
         """
@@ -76,24 +105,51 @@
             self.environmentsComboBox.addItem(projectVenv)
         self.environmentsComboBox.addItems(self.__pip.getVirtualenvNames())
     
+    #######################################################################
+    ## Slots handling widget signals below
+    #######################################################################
+    
+    def __selectedUpdateableItems(self):
+        """
+        Private method to get a list of selected items that can be updated.
+        
+        @return list of selected items that can be updated
+        @rtype list of QTreeWidgetItem
+        """
+        return [
+            itm for itm in self.packagesList.selectedItems()
+            if bool(itm.text(2))
+        ]
+    
+    def __allUpdateableItems(self):
+        """
+        Private method to get a list of all items that can be updated.
+        
+        @return list of all items that can be updated
+        @rtype list of QTreeWidgetItem
+        """
+        updateableItems = []
+        for index in range(self.packagesList.topLevelItemCount()):
+            itm = self.packagesList.topLevelItem(index)
+            if itm.text(2):
+                updateableItems.append(itm)
+        
+        return updateableItems
+    
     def __updateActionButtons(self):
         """
         Private method to set the state of the action buttons.
         """
-        # TODO: not yet implemented
-        pass
-    
-    #######################################################################
-    ## Slots handling widget signals below
-    #######################################################################
+        self.upgradeButton.setEnabled(
+            bool(self.__selectedUpdateableItems()))
+        self.uninstallButton.setEnabled(
+            bool(self.packagesList.selectedItems()))
+        self.upgradeAllButton.setEnabled(
+            bool(self.__allUpdateableItems()))
     
-    @pyqtSlot(int)
-    def on_environmentsComboBox_currentIndexChanged(self, index):
+    def __refreshPackagesList(self):
         """
-        Private slot handling the selection of a conda environment.
-        
-        @param index index of the selected conda environment
-        @type int
+        Private method to referesh the packages list.
         """
         self.packagesList.clear()
         venvName = self.environmentsComboBox.currentText()
@@ -107,10 +163,34 @@
                 QApplication.processEvents()
                 
                 # 1. populate with installed packages
-                pass    # TODO: add code to list installed
+                self.packagesList.setUpdatesEnabled(False)
+                installedPackages = self.__pip.getInstalledPackages(
+                    venvName,
+                    localPackages=self.localCheckBox.isChecked(),
+                    notRequired=self.notRequiredCheckBox.isChecked(),
+                    usersite=self.userCheckBox.isChecked(),
+                )
+                for package, version in installedPackages:
+                    QTreeWidgetItem(self.packagesList, [package, version])
+                self.packagesList.setUpdatesEnabled(True)
+                self.statusLabel.setText(
+                    self.tr("Getting outdated packages..."))
+                QApplication.processEvents()
                 
                 # 2. update with update information
-                pass    # TODO: add code to list outdated
+                self.packagesList.setUpdatesEnabled(False)
+                outdatedPackages = self.__pip.getOutdatedPackages(
+                    venvName,
+                    localPackages=self.localCheckBox.isChecked(),
+                    notRequired=self.notRequiredCheckBox.isChecked(),
+                    usersite=self.userCheckBox.isChecked(),
+                )
+                for package, _version, latest in outdatedPackages:
+                    items = self.packagesList.findItems(
+                        package, Qt.MatchExactly | Qt.MatchCaseSensitive)
+                    if items:
+                        itm = items[0]
+                        itm.setText(2, latest)
                 
                 self.packagesList.sortItems(0, Qt.AscendingOrder)
                 for col in range(self.packagesList.columnCount()):
@@ -122,6 +202,205 @@
         self.__updateActionButtons()
         self.__updateSearchActionButtons()
     
+    @pyqtSlot(int)
+    def on_environmentsComboBox_currentIndexChanged(self, index):
+        """
+        Private slot handling the selection of a conda environment.
+        
+        @param index index of the selected conda environment
+        @type int
+        """
+        self.__refreshPackagesList()
+    
+    @pyqtSlot(bool)
+    def on_localCheckBox_clicked(self, checked):
+        """
+        Private slot handling the switching of the local mode.
+        
+        @param checked state of the local check box
+        @type bool
+        """
+        self.__refreshPackagesList()
+    
+    @pyqtSlot(bool)
+    def on_notRequiredCheckBox_clicked(self, checked):
+        """
+        Private slot handling the switching of the 'not required' mode.
+        
+        @param checked state of the 'not required' check box
+        @type bool
+        """
+        self.__refreshPackagesList()
+    
+    @pyqtSlot(bool)
+    def on_userCheckBox_clicked(self, checked):
+        """
+        Private slot handling the switching of the 'user-site' mode.
+        
+        @param checked state of the 'user-site' check box
+        @type bool
+        """
+        self.__refreshPackagesList()
+    
+    @pyqtSlot()
+    def on_packagesList_itemSelectionChanged(self):
+        """
+        Private slot handling the selection of a package.
+        """
+        self.infoWidget.clear()
+        
+        if len(self.packagesList.selectedItems()) == 1:
+            itm = self.packagesList.selectedItems()[0]
+            
+            environment = self.environmentsComboBox.currentText()
+            interpreter = self.__pip.getVirtualenvInterpreter(environment)
+            if not interpreter:
+                return
+            
+            QApplication.setOverrideCursor(Qt.WaitCursor)
+            
+            args = ["-m", "pip", "show"]
+            if self.verboseCheckBox.isChecked():
+                args.append("--verbose")
+            if self.installedFilesCheckBox.isChecked():
+                args.append("--files")
+            args.append(itm.text(0))
+            success, output = self.__pip.runProcess(args, interpreter)
+            
+            if success and output:
+                mode = self.ShowProcessGeneralMode
+                for line in output.splitlines():
+                    line = line.rstrip()
+                    if line != "---":
+                        if mode != self.ShowProcessGeneralMode:
+                            if line[0] == " ":
+                                QTreeWidgetItem(
+                                    self.infoWidget,
+                                    [" ", line.strip()])
+                            else:
+                                mode = self.ShowProcessGeneralMode
+                        if mode == self.ShowProcessGeneralMode:
+                            try:
+                                label, info = line.split(": ", 1)
+                            except ValueError:
+                                label = line[:-1]
+                                info = ""
+                            label = label.lower()
+                            if label in self.__infoLabels:
+                                QTreeWidgetItem(
+                                    self.infoWidget,
+                                    [self.__infoLabels[label], info])
+                            if label == "files":
+                                mode = self.ShowProcessFilesListMode
+                            elif label == "classifiers":
+                                mode = self.ShowProcessClassifiersMode
+                            elif label == "entry-points":
+                                mode = self.ShowProcessEntryPointsMode
+                self.infoWidget.scrollToTop()
+            
+            header = self.infoWidget.header()
+            header.setStretchLastSection(False)
+            header.resizeSections(QHeaderView.ResizeToContents)
+            if header.sectionSize(0) + header.sectionSize(1) < header.width():
+                header.setStretchLastSection(True)
+            
+            QApplication.restoreOverrideCursor()
+        
+        self.__updateActionButtons()
+    
+    @pyqtSlot(bool)
+    def on_verboseCheckBox_clicked(self, checked):
+        """
+        Private slot to handle a change of the verbose package information
+        checkbox.
+        
+        @param checked state of the checkbox
+        @type bool
+        """
+        self.on_packagesList_itemSelectionChanged()
+    
+    @pyqtSlot(bool)
+    def on_installedFilesCheckBox_clicked(self, checked):
+        """
+        Private slot to handle a change of the installed files information
+        checkbox.
+        
+        @param checked state of the checkbox
+        @type bool
+        """
+        self.on_packagesList_itemSelectionChanged()
+    
+    @pyqtSlot()
+    def on_refreshButton_clicked(self):
+        """
+        Private slot to refresh the display.
+        """
+        currentEnvironment = self.environmentsComboBox.currentText()
+        self.environmentsComboBox.clear()
+        self.packagesList.clear()
+        
+        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
+        QApplication.processEvents()
+        
+        self.__populateEnvironments()
+        
+        index = self.environmentsComboBox.findText(
+            currentEnvironment, Qt.MatchExactly | Qt.MatchCaseSensitive)
+        if index != -1:
+            self.environmentsComboBox.setCurrentIndex(index)
+        
+        QApplication.restoreOverrideCursor()
+        self.__updateActionButtons()
+    
+    @pyqtSlot()
+    def on_upgradeButton_clicked(self):
+        """
+        Private slot to upgrade selected packages of the selected environment.
+        """
+        packages = [itm.text(0) for itm in self.__selectedUpdateableItems()]
+        if packages:
+            ok = self.__executeUpgradePackages(packages)
+            if ok:
+                self.on_refreshButton_clicked()
+    
+    @pyqtSlot()
+    def on_upgradeAllButton_clicked(self):
+        """
+        Private slot to upgrade all packages of the selected environment.
+        """
+        packages = [itm.text(0) for itm in self.__allUpdateableItems()]
+        if packages:
+            ok = self.__executeUpgradePackages(packages)
+            if ok:
+                self.on_refreshButton_clicked()
+    
+    @pyqtSlot()
+    def on_uninstallButton_clicked(self):
+        """
+        Private slot to remove selected packages of the selected environment.
+        """
+        packages = [itm.text(0) for itm in self.packagesList.selectedItems()]
+        if packages:
+            ok = self.__pip.uninstallPackages(
+                packages,
+                venvName=self.environmentsComboBox.currentText())
+            if ok:
+                self.on_refreshButton_clicked()
+    
+    def __executeUpgradePackages(self, packages):
+        """
+        Private method to execute the pip upgrade command.
+        
+        @param packages list of package names to be upgraded
+        @type list of str
+        @return flag indicating success
+        @rtype bool
+        """
+        ok = self.__pip.upgradePackages(
+            packages, venvName=self.environmentsComboBox.currentText(),
+            userSite=self.userCheckBox.isChecked())
+        return ok
+    
     #######################################################################
     ## Search widget related methods below
     #######################################################################
@@ -156,4 +435,14 @@
     #######################################################################
     ## Menu related methods below
     #######################################################################
-    
+        
+    def __initPipMenu(self):
+        """
+        Private method to create the super menu and attach it to the super
+        menu button.
+        """
+        self.__pip.initActions()
+        
+        self.__pipMenu = self.__pip.initMenu()
+        
+        self.pipMenuButton.setMenu(self.__pipMenu)

eric ide

mercurial