Plugins/UiExtensionPlugins/PipInterface/PipListDialog.py

changeset 6011
e6af0dcfbb35
child 6048
82ad8ec9548c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/UiExtensionPlugins/PipInterface/PipListDialog.py	Sat Dec 09 18:32:08 2017 +0100
@@ -0,0 +1,516 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015 - 2017 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing a dialog to list installed packages.
+"""
+
+from __future__ import unicode_literals
+try:
+    str = unicode       # __IGNORE_EXCEPTION__
+except NameError:
+    pass
+
+import json
+
+from PyQt5.QtCore import pyqtSlot, Qt, QProcess, QTimer
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton, \
+    QApplication, QTreeWidgetItem, QHeaderView
+
+from E5Gui import E5MessageBox
+
+from .Ui_PipListDialog import Ui_PipListDialog
+
+import Preferences
+
+
+class PipListDialog(QDialog, Ui_PipListDialog):
+    """
+    Class implementing a dialog to list installed packages.
+    """
+    CommandArguments = {
+        "list": ["list", "--format=json"],
+        "uptodate": ["list", "--uptodate", "--format=json"],
+        "outdated": ["list", "--outdated", "--format=json"],
+    }
+    
+    ShowProcessGeneralMode = 0
+    ShowProcessClassifiersMode = 1
+    ShowProcessEntryPointsMode = 2
+    ShowProcessFilesListMode = 3
+    
+    def __init__(self, pip, mode, plugin, title, parent=None):
+        """
+        Constructor
+        
+        @param pip reference to the master object (Pip)
+        @param mode list command mode (string; one of 'list',
+            'uptodate', 'outdated')
+        @param plugin reference to the plugin object (ToolPipPlugin)
+        @param title title of the dialog (string)
+        @param parent reference to the parent widget (QWidget)
+        """
+        assert mode in PipListDialog.CommandArguments
+        
+        super(PipListDialog, self).__init__(parent)
+        self.setupUi(self)
+        self.setWindowFlags(Qt.Window)
+        
+        self.setWindowTitle(title)
+        
+        self.__refreshButton = self.buttonBox.addButton(
+            self.tr("&Refresh"), QDialogButtonBox.ActionRole)
+        self.__refreshButton.setEnabled(False)
+        if mode == "outdated":
+            self.__upgradeButton = self.buttonBox.addButton(
+                self.tr("Up&grade"), QDialogButtonBox.ActionRole)
+            self.__upgradeButton.setEnabled(False)
+            self.__upgradeAllButton = self.buttonBox.addButton(
+                self.tr("Upgrade &All"), QDialogButtonBox.ActionRole)
+            self.__upgradeAllButton.setEnabled(False)
+        else:
+            self.__upgradeButton = None
+            self.__upgradeAllButton = None
+        self.__uninstallButton = self.buttonBox.addButton(
+            self.tr("&Uninstall"), QDialogButtonBox.ActionRole)
+        self.__uninstallButton.setEnabled(False)
+        
+        self.__pip = pip
+        self.__mode = mode
+        self.__defaultCommand = plugin.getPreferences("CurrentPipExecutable")
+        self.__ioEncoding = Preferences.getSystem("IOEncoding")
+        self.__errors = ""
+        self.__output = []
+        
+        self.__nothingStrings = {
+            "list": self.tr("Nothing to show"),
+            "uptodate": self.tr("All packages outdated"),
+            "outdated": self.tr("All packages up-to-date"),
+        }
+        
+        self.__default = self.tr("<Default>")
+        pipExecutables = sorted(plugin.getPreferences("PipExecutables"))
+        self.pipComboBox.addItem(self.__default)
+        self.pipComboBox.addItems(pipExecutables)
+        
+        if mode == "list":
+            self.infoLabel.setText(self.tr("Installed Packages:"))
+            self.packageList.setHeaderLabels([
+                self.tr("Package"),
+                self.tr("Version"),
+            ])
+        elif mode == "uptodate":
+            self.infoLabel.setText(self.tr("Up-to-date Packages:"))
+            self.packageList.setHeaderLabels([
+                self.tr("Package"),
+                self.tr("Version"),
+            ])
+        elif mode == "outdated":
+            self.infoLabel.setText(self.tr("Outdated Packages:"))
+            self.packageList.setHeaderLabels([
+                self.tr("Package"),
+                self.tr("Current Version"),
+                self.tr("Latest Version"),
+                self.tr("Package Type"),
+            ])
+        
+        self.packageList.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"])
+        
+        self.process = QProcess()
+        self.process.finished.connect(self.__procFinished)
+        self.process.readyReadStandardOutput.connect(self.__readStdout)
+        self.process.readyReadStandardError.connect(self.__readStderr)
+        
+        self.show()
+        QApplication.processEvents()
+    
+    def __stopProcess(self):
+        """
+        Private slot to stop the running process.
+        """
+        if self.process.state() != QProcess.NotRunning:
+            self.process.terminate()
+            QTimer.singleShot(2000, self.process.kill)
+            self.process.waitForFinished(3000)
+        
+        QApplication.restoreOverrideCursor()
+    
+    def closeEvent(self, e):
+        """
+        Protected slot implementing a close event handler.
+        
+        @param e close event (QCloseEvent)
+        """
+        self.__stopProcess()
+        e.accept()
+    
+    def __finish(self):
+        """
+        Private slot called when the process finished or the user pressed
+        the cancel button.
+        """
+        self.__stopProcess()
+        
+        self.__processOutput()
+        
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
+        self.buttonBox.button(QDialogButtonBox.Close).setFocus(
+            Qt.OtherFocusReason)
+        self.__refreshButton.setEnabled(True)
+        
+        if self.packageList.topLevelItemCount() == 0:
+            QTreeWidgetItem(self.packageList,
+                            [self.__nothingStrings[self.__mode]])
+            if self.__errors and not self.__errors.startswith("DEPRECATION"):
+                E5MessageBox.critical(
+                    self,
+                    self.windowTitle(),
+                    self.tr("""<p>The pip command failed.</p>"""
+                            """<p>Reason: {0}</p>""").format(
+                        self.__errors.replace("\r\n", "<br/>")
+                            .replace("\n", "<br/>").replace("\r", "<br/>")
+                            .replace(" ", "&nbsp;")))
+            if self.__upgradeAllButton is not None:
+                self.__upgradeAllButton.setEnabled(False)
+        else:
+            if self.__upgradeAllButton is not None:
+                self.__upgradeAllButton.setEnabled(True)
+        
+        self.packageList.sortItems(
+            0,
+            self.packageList.header().sortIndicatorOrder())
+        self.packageList.header().resizeSections(
+            QHeaderView.ResizeToContents)
+        self.packageList.header().setStretchLastSection(True)
+    
+    @pyqtSlot(QAbstractButton)
+    def on_buttonBox_clicked(self, button):
+        """
+        Private slot called by a button of the button box clicked.
+        
+        @param button button that was clicked (QAbstractButton)
+        """
+        if button == self.buttonBox.button(QDialogButtonBox.Close):
+            self.close()
+        elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
+            self.__finish()
+        elif button == self.__refreshButton:
+            self.__refresh()
+        elif button == self.__upgradeButton:
+            self.__upgradePackages()
+        elif button == self.__upgradeAllButton:
+            self.__upgradeAllPackages()
+        elif button == self.__uninstallButton:
+            self.__uninstallPackages()
+    
+    def __procFinished(self, exitCode, exitStatus):
+        """
+        Private slot connected to the finished signal.
+        
+        @param exitCode exit code of the process (integer)
+        @param exitStatus exit status of the process (QProcess.ExitStatus)
+        """
+        self.__finish()
+    
+    def __refresh(self):
+        """
+        Private slot to refresh the displayed list.
+        """
+        self.__stopProcess()
+        self.start()
+    
+    def start(self):
+        """
+        Public method to start the command.
+        """
+        self.packageList.clear()
+        self.__errors = ""
+        self.__output = []
+        
+        self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
+        self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
+        self.__refreshButton.setEnabled(False)
+        QApplication.processEvents()
+        
+        QApplication.setOverrideCursor(Qt.WaitCursor)
+        QApplication.processEvents()
+        
+        command = self.pipComboBox.currentText()
+        if command == self.__default:
+            command = self.__defaultCommand
+        
+        args = PipListDialog.CommandArguments[self.__mode][:]
+        if self.localCheckBox.isChecked():
+            args.append("--local")
+        if self.notRequiredCheckBox.isChecked():
+            args.append("--not-required")
+        
+        self.process.start(command, args)
+        procStarted = self.process.waitForStarted(5000)
+        if not procStarted:
+            self.buttonBox.setFocus()
+            self.__stopProcess()
+            E5MessageBox.critical(
+                self,
+                self.tr('Process Generation Error'),
+                self.tr(
+                    'The process {0} could not be started.'
+                ).format(command))
+            self.__finish()
+    
+    def __processOutput(self):
+        """
+        Private method to process the captured output.
+        """
+        if self.__output:
+            packageData = json.loads("\n".join(self.__output))
+            for package in packageData:
+                data = [
+                    package["name"],
+                    package["version"],
+                ]
+                if self.__mode == "outdated":
+                    data.extend([
+                        package["latest_version"],
+                        package["latest_filetype"],
+                    ])
+                QTreeWidgetItem(self.packageList, data)
+    
+    def __readStdout(self):
+        """
+        Private slot to handle the readyReadStandardOutput signal.
+        
+        It reads the output of the process, formats it and inserts it into
+        the contents pane.
+        """
+        self.process.setReadChannel(QProcess.StandardOutput)
+        
+        while self.process.canReadLine():
+            line = str(self.process.readLine(), self.__ioEncoding,
+                       'replace').strip()
+            self.__output.append(line)
+    
+    def __readStderr(self):
+        """
+        Private slot to handle the readyReadStandardError signal.
+        
+        It reads the error output of the process and inserts it into the
+        error pane.
+        """
+        self.__errors += str(self.process.readAllStandardError(),
+                             self.__ioEncoding, 'replace')
+    
+    @pyqtSlot(str)
+    def on_pipComboBox_activated(self, txt):
+        """
+        Private slot handling the selection of a pip executable.
+        
+        @param txt path of the pip executable (string)
+        """
+        self.__refresh()
+    
+    @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 (boolean)
+        """
+        self.__refresh()
+    
+    @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 (boolean)
+        """
+        self.__refresh()
+    
+    @pyqtSlot()
+    def on_packageList_itemSelectionChanged(self):
+        """
+        Private slot handling the selection of a package.
+        """
+        self.infoWidget.clear()
+        
+        if len(self.packageList.selectedItems()) == 1:
+            itm = self.packageList.selectedItems()[0]
+            
+            command = self.pipComboBox.currentText()
+            if command == self.__default:
+                command = ""
+            
+            QApplication.setOverrideCursor(Qt.WaitCursor)
+            
+            args = ["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, cmd=command)
+            
+            if success and output:
+                mode = PipListDialog.ShowProcessGeneralMode
+                for line in output.splitlines():
+                    line = line.rstrip()
+                    if line != "---":
+                        if mode != PipListDialog.ShowProcessGeneralMode:
+                            if line[0] == " ":
+                                QTreeWidgetItem(
+                                    self.infoWidget,
+                                    [" ", line.strip()])
+                            else:
+                                mode = PipListDialog.ShowProcessGeneralMode
+                        if mode == PipListDialog.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 = PipListDialog.ShowProcessFilesListMode
+                            elif label == "classifiers":
+                                mode = PipListDialog.ShowProcessClassifiersMode
+                            elif label == "entry-points":
+                                mode = PipListDialog.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()
+        
+        enable = (len(self.packageList.selectedItems()) > 1 or
+                  (len(self.packageList.selectedItems()) == 1 and
+                   self.packageList.selectedItems()[0].text(0) not in
+                   self.__nothingStrings.values()))
+        self.__upgradeButton and self.__upgradeButton.setEnabled(enable)
+        self.__uninstallButton.setEnabled(enable)
+    
+    @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_packageList_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_packageList_itemSelectionChanged()
+    
+    def __upgradePackages(self):
+        """
+        Private slot to upgrade the selected packages.
+        """
+        packages = []
+        for itm in self.packageList.selectedItems():
+            packages.append(itm.text(0))
+        
+        if packages:
+            if "pip" in packages:
+                self.__upgradePip()
+            else:
+                self.__executeUpgradePackages(packages)
+    
+    def __upgradeAllPackages(self):
+        """
+        Private slot to upgrade all listed packages.
+        """
+        packages = []
+        for index in range(self.packageList.topLevelItemCount()):
+            itm = self.packageList.topLevelItem(index)
+            packages.append(itm.text(0))
+        
+        if packages:
+            if "pip" in packages:
+                self.__upgradePip()
+            else:
+                self.__executeUpgradePackages(packages)
+    
+    def __upgradePip(self):
+        """
+        Private slot to upgrade pip itself.
+        """
+        pip = self.pipComboBox.currentText()
+        if pip == self.__default:
+            pip = ""
+
+        res = self.__pip.upgradePip(pip=pip)
+        if res:
+            self.__refresh()
+
+    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
+        """
+        command = self.pipComboBox.currentText()
+        if command == self.__default:
+            command = ""
+
+        res = self.__pip.upgradePackages(packages, cmd=command)
+        if res:
+            self.__refresh()
+    
+    def __uninstallPackages(self):
+        """
+        Private slot to uninstall the selected packages.
+        """
+        packages = []
+        for itm in self.packageList.selectedItems():
+            packages.append(itm.text(0))
+        
+        if packages:
+            command = self.pipComboBox.currentText()
+            if command == self.__default:
+                command = ""
+            
+            res = self.__pip.uninstallPackages(packages, cmd=command)
+            if res:
+                self.__refresh()

eric ide

mercurial