--- 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)