Tue, 19 Feb 2019 19:56:24 +0100
PipInterface: continued with the pip interface widget.
PipInterface/Pip.py | file | annotate | diff | comparison | revisions | |
PipInterface/PipListDialog.py | file | annotate | diff | comparison | revisions | |
PipInterface/PipListDialog.ui | file | annotate | diff | comparison | revisions | |
PipInterface/PipPackagesWidget.py | file | annotate | diff | comparison | revisions | |
VirtualEnv/VirtualenvManager.py | file | annotate | diff | comparison | revisions | |
eric6.e4p | file | annotate | diff | comparison | revisions |
--- a/PipInterface/Pip.py Mon Feb 18 19:49:43 2019 +0100 +++ b/PipInterface/Pip.py Tue Feb 19 19:56:24 2019 +0100 @@ -15,6 +15,7 @@ import os import sys +import json from PyQt5.QtCore import pyqtSlot, QObject, QProcess from PyQt5.QtWidgets import QMenu, QInputDialog, QDialog @@ -45,7 +46,7 @@ """ super(Pip, self).__init__(parent) - self.__virtualenvManager = e5App().getObject("VirtualEnvManager") +## self.__virtualenvManager = e5App().getObject("VirtualEnvManager") ## self.__project = e5App().getObject("Project") self.__menus = {} # dictionary with references to menus @@ -545,11 +546,11 @@ if not venvName: self.__selectPipVirtualenv() venvName = Preferences.getPip("CurrentEnvironment") - if self.__virtualenvManager.isGlobalEnvironment(venvName): + venvManager = e5App().getObject("VirtualEnvManager") + if venvManager.isGlobalEnvironment(venvName): venvDirectory = self.__getUserConfig() else: - venvDirectory = \ - self.__virtualenvManager.getVirtualenvDirectory(venvName) + venvDirectory = venvManager.getVirtualenvDirectory(venvName) if venvDirectory: config = os.path.join(venvDirectory, pip) @@ -591,13 +592,15 @@ if venvName == self.getDefaultEnvironmentString(): venvName = Preferences.getPip("CurrentEnvironment") elif venvName == self.getProjectEnvironmentString(): - venvName = e5App().getObject("Project").getDebugProperty("VIRTUALENV") + venvName = \ + e5App().getObject("Project").getDebugProperty("VIRTUALENV") if not venvName: # fall back to standard if not defined venvName = Preferences.getPip("CurrentEnvironment") - interpreter = self.__virtualenvManager.getVirtualenvInterpreter( - venvName) + interpreter = \ + e5App().getObject("VirtualEnvManager").getVirtualenvInterpreter( + venvName) if not interpreter: E5MessageBox.critical( None, @@ -614,7 +617,8 @@ @return sorted list of virtual environment names @rtype list of str """ - return sorted(self.__virtualenvManager.getVirtualenvNames()) + return sorted( + e5App().getObject("VirtualEnvManager").getVirtualenvNames()) ########################################################################## ## Methods below implement the individual menu entries @@ -1130,6 +1134,121 @@ self.__searchDialog = PipSearchDialog(self, indexUrl) self.__searchDialog.show() + def getInstalledPackages(self, envName, localPackages=True, + notRequired=False, usersite=False): + """ + Public method to get the list of installed packages. + + @param envName name of the environment to get the packages for + @type str + @param localPackages flag indicating to get local packages only + @type bool + @param notRequired flag indicating to list packages that are not + dependencies of installed packages as well + @type bool + @param usersite flag indicating to only list packages installed + in user-site + @type bool + @return list of tuples containing the package name and version + @rtype list of tuple of (str, str) + """ + packages = [] + + if envName: + interpreter = self.getVirtualenvInterpreter(envName) + if interpreter: + args = [ + "-m", "pip", + "list", + "--format=json", + ] + if localPackages: + args.append("--local") + if notRequired: + args.append("--not-required") + if usersite: + args.append("--user") + + proc = QProcess() + proc.start(interpreter, args) + if proc.waitForStarted(15000): + if proc.waitForFinished(30000): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + jsonList = json.loads(output) + except Exception: + jsonList = [] + + for package in jsonList: + if isinstance(package, dict): + packages.append(( + package["name"], + package["version"], + )) + + return packages + + def getOutdatedPackages(self, envName, localPackages=True, + notRequired=False, usersite=False): + """ + Public method to get the list of outdated packages. + + @param envName name of the environment to get the packages for + @type str + @param localPackages flag indicating to get local packages only + @type bool + @param notRequired flag indicating to list packages that are not + dependencies of installed packages as well + @type bool + @param usersite flag indicating to only list packages installed + in user-site + @type bool + @return list of tuples containing the package name, installed version + and available version + @rtype list of tuple of (str, str, str) + """ + packages = [] + + if envName: + interpreter = self.getVirtualenvInterpreter(envName) + if interpreter: + args = [ + "-m", "pip", + "list", + "--outdated", + "--format=json", + ] + if localPackages: + args.append("--local") + if notRequired: + args.append("--not-required") + if usersite: + args.append("--user") + + proc = QProcess() + proc.start(interpreter, args) + if proc.waitForStarted(15000): + if proc.waitForFinished(30000): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + jsonList = json.loads(output) + except Exception: + jsonList = [] + + for package in jsonList: + if isinstance(package, dict): + packages.append(( + package["name"], + package["version"], + package["latest_version"], + )) + + return packages + def __pipConfigure(self): """ Private slot to open the configuration page.
--- a/PipInterface/PipListDialog.py Mon Feb 18 19:49:43 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,551 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2015 - 2019 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, indexUrl, title, parent=None): - """ - Constructor - - @param pip reference to the master object - @type Pip - @param mode list command mode (one of 'list', 'uptodate', 'outdated') - @type str - @param indexUrl URL of the pypi index - @type str - @param title title of the dialog - @type str - @param parent reference to the parent widget - @type 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.__ioEncoding = Preferences.getSystem("IOEncoding") - self.__indexUrl = indexUrl - 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.venvComboBox.addItem(self.__pip.getDefaultEnvironmentString()) - projectVenv = self.__pip.getProjectEnvironmentString() - if projectVenv: - self.venvComboBox.addItem(projectVenv) - self.venvComboBox.addItems(self.__pip.getVirtualenvNames()) - - 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 - @type 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 command failed.</p>""" - """<p>Reason: {0}</p>""").format( - self.__errors.replace("\r\n", "<br/>") - .replace("\n", "<br/>").replace("\r", "<br/>") - .replace(" ", " "))) - 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 - @type 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 - @type int - @param exitStatus exit status of the process - @type 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) - if self.__upgradeAllButton is not None: - self.__upgradeAllButton.setEnabled(False) - QApplication.processEvents() - - QApplication.setOverrideCursor(Qt.WaitCursor) - QApplication.processEvents() - - venvName = self.venvComboBox.currentText() - interpreter = self.__pip.getVirtualenvInterpreter(venvName) - if not interpreter: - return - - args = ["-m", "pip"] + PipListDialog.CommandArguments[self.__mode] - if self.localCheckBox.isChecked(): - args.append("--local") - if self.notRequiredCheckBox.isChecked(): - args.append("--not-required") - if self.userCheckBox.isChecked(): - args.append("--user") - - if self.__indexUrl: - args.append("--index-url") - args.append(self.__indexUrl + "/simple") - - self.process.start(interpreter, 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(interpreter)) - self.__finish() - - def __processOutput(self): - """ - Private method to process the captured output. - """ - if self.__output: - try: - 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) - except ValueError as err: - self.__errors += str(err) + "\n" - self.__errors += "received output:\n" - self.__errors += "\n".join(self.__output) - - 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_venvComboBox_activated(self, txt): - """ - Private slot handling the selection of a virtual environment. - - @param txt virtual environment - @type str - """ - 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 - @type bool - """ - 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 - @type bool - """ - self.__refresh() - - @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.__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] - - environment = self.venvComboBox.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 = 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. - """ - res = self.__pip.upgradePip( - venvName=self.venvComboBox.currentText(), - userSite=self.userCheckBox.isChecked()) - 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 - """ - res = self.__pip.upgradePackages( - packages, venvName=self.venvComboBox.currentText(), - userSite=self.userCheckBox.isChecked()) - if res: - self.activateWindow() - self.raise_() - 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: - res = self.__pip.uninstallPackages( - packages, - venvName=self.venvComboBox.currentText()) - if res: - self.activateWindow() - self.raise_() - self.__refresh()
--- a/PipInterface/PipListDialog.ui Mon Feb 18 19:49:43 2019 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,253 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>PipListDialog</class> - <widget class="QDialog" name="PipListDialog"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>600</width> - <height>650</height> - </rect> - </property> - <property name="windowTitle"> - <string>Package List</string> - </property> - <property name="sizeGripEnabled"> - <bool>true</bool> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string>Virtual Environment:</string> - </property> - </widget> - </item> - <item> - <widget class="QComboBox" name="venvComboBox"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="toolTip"> - <string>Select the virtual environment to be used</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="infoLabel"> - <property name="text"> - <string notr="true">TextLabel</string> - </property> - </widget> - </item> - <item row="0" column="1"> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item row="0" column="2"> - <widget class="QCheckBox" name="localCheckBox"> - <property name="toolTip"> - <string>Select to show only locally-installed packages</string> - </property> - <property name="text"> - <string>Local packages only</string> - </property> - <property name="checked"> - <bool>true</bool> - </property> - </widget> - </item> - <item row="0" column="3"> - <widget class="QCheckBox" name="notRequiredCheckBox"> - <property name="toolTip"> - <string>Select to list packages that are not dependencies of installed packages</string> - </property> - <property name="text"> - <string>Not required Packages</string> - </property> - </widget> - </item> - <item row="1" column="2"> - <widget class="QCheckBox" name="userCheckBox"> - <property name="toolTip"> - <string>Select to show only packages installed to the user-site</string> - </property> - <property name="text"> - <string>User-Site only</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QSplitter" name="splitter"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="childrenCollapsible"> - <bool>false</bool> - </property> - <widget class="QTreeWidget" name="packageList"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>3</verstretch> - </sizepolicy> - </property> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="selectionMode"> - <enum>QAbstractItemView::ExtendedSelection</enum> - </property> - <property name="rootIsDecorated"> - <bool>false</bool> - </property> - <property name="itemsExpandable"> - <bool>false</bool> - </property> - <property name="sortingEnabled"> - <bool>true</bool> - </property> - <column> - <property name="text"> - <string notr="true">1</string> - </property> - </column> - </widget> - <widget class="QWidget" name="widget" native="true"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>1</verstretch> - </sizepolicy> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_2"> - <item> - <widget class="QCheckBox" name="verboseCheckBox"> - <property name="toolTip"> - <string>Select to show verbose package information</string> - </property> - <property name="text"> - <string>Verbose Package Information</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="installedFilesCheckBox"> - <property name="toolTip"> - <string>Select to show information about installed files</string> - </property> - <property name="text"> - <string>Installed Files Information</string> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QTreeWidget" name="infoWidget"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> - <horstretch>0</horstretch> - <verstretch>1</verstretch> - </sizepolicy> - </property> - <property name="alternatingRowColors"> - <bool>true</bool> - </property> - <property name="rootIsDecorated"> - <bool>false</bool> - </property> - <property name="itemsExpandable"> - <bool>false</bool> - </property> - <property name="allColumnsShowFocus"> - <bool>true</bool> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - <property name="columnCount"> - <number>2</number> - </property> - <attribute name="headerVisible"> - <bool>false</bool> - </attribute> - <attribute name="headerStretchLastSection"> - <bool>false</bool> - </attribute> - <column> - <property name="text"> - <string notr="true">1</string> - </property> - </column> - <column> - <property name="text"> - <string notr="true">2</string> - </property> - </column> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - <item> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set> - </property> - </widget> - </item> - </layout> - </widget> - <tabstops> - <tabstop>venvComboBox</tabstop> - <tabstop>localCheckBox</tabstop> - <tabstop>notRequiredCheckBox</tabstop> - <tabstop>userCheckBox</tabstop> - <tabstop>packageList</tabstop> - <tabstop>verboseCheckBox</tabstop> - <tabstop>installedFilesCheckBox</tabstop> - <tabstop>infoWidget</tabstop> - </tabstops> - <resources/> - <connections/> -</ui>
--- 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)
--- a/VirtualEnv/VirtualenvManager.py Mon Feb 18 19:49:43 2019 +0100 +++ b/VirtualEnv/VirtualenvManager.py Tue Feb 19 19:56:24 2019 +0100 @@ -15,7 +15,7 @@ import json import copy -from PyQt5.QtCore import pyqtSlot, QObject +from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject from PyQt5.QtWidgets import QDialog from E5Gui import E5MessageBox @@ -27,9 +27,20 @@ class VirtualenvManager(QObject): """ Class implementing an object to manage Python virtual environments. + + @signal virtualEnvironmentAdded() emitted to indicate the addition of + a virtual environment + @signal virtualEnvironmentRemoved() emitted to indicate the removal and + deletion of a virtual environment + @signal virtualEnvironmentChanged(name) emitted to indicate a change of + a virtual environment """ DefaultKey = "<default>" + virtualEnvironmentAdded = pyqtSignal() + virtualEnvironmentRemoved = pyqtSignal() + virtualEnvironmentChanged = pyqtSignal(str) + def __init__(self, parent=None): """ Constructor @@ -234,6 +245,7 @@ self.__saveSettings() + self.virtualEnvironmentAdded.emit() if self.__virtualenvManagerDialog: self.__virtualenvManagerDialog.refresh() @@ -283,6 +295,7 @@ self.__saveSettings() + self.virtualEnvironmentChanged.emit(venvName) if self.__virtualenvManagerDialog: self.__virtualenvManagerDialog.refresh() @@ -368,6 +381,7 @@ self.__saveSettings() + self.virtualEnvironmentRemoved.emit() if self.__virtualenvManagerDialog: self.__virtualenvManagerDialog.refresh() @@ -421,6 +435,7 @@ self.__saveSettings() + self.virtualEnvironmentRemoved.emit() if self.__virtualenvManagerDialog: self.__virtualenvManagerDialog.refresh()
--- a/eric6.e4p Mon Feb 18 19:49:43 2019 +0100 +++ b/eric6.e4p Tue Feb 19 19:56:24 2019 +0100 @@ -475,7 +475,6 @@ <Source>PipInterface/PipDialog.py</Source> <Source>PipInterface/PipFileSelectionDialog.py</Source> <Source>PipInterface/PipFreezeDialog.py</Source> - <Source>PipInterface/PipListDialog.py</Source> <Source>PipInterface/PipPackageDetailsDialog.py</Source> <Source>PipInterface/PipPackagesInputDialog.py</Source> <Source>PipInterface/PipPackagesWidget.py</Source> @@ -1828,7 +1827,6 @@ <Form>PipInterface/PipDialog.ui</Form> <Form>PipInterface/PipFileSelectionDialog.ui</Form> <Form>PipInterface/PipFreezeDialog.ui</Form> - <Form>PipInterface/PipListDialog.ui</Form> <Form>PipInterface/PipPackageDetailsDialog.ui</Form> <Form>PipInterface/PipPackagesInputDialog.ui</Form> <Form>PipInterface/PipPackagesWidget.ui</Form> @@ -2262,14 +2260,14 @@ </Resources> <Others> <Other>.hgignore</Other> + <Other>APIs/Python/zope-2.10.7.api</Other> + <Other>APIs/Python/zope-2.11.2.api</Other> + <Other>APIs/Python/zope-3.3.1.api</Other> <Other>APIs/Python3/PyQt4.bas</Other> <Other>APIs/Python3/PyQt5.bas</Other> <Other>APIs/Python3/QScintilla2.bas</Other> <Other>APIs/Python3/eric6.api</Other> <Other>APIs/Python3/eric6.bas</Other> - <Other>APIs/Python/zope-2.10.7.api</Other> - <Other>APIs/Python/zope-2.11.2.api</Other> - <Other>APIs/Python/zope-3.3.1.api</Other> <Other>APIs/QSS/qss.api</Other> <Other>APIs/Ruby/Ruby-1.8.7.api</Other> <Other>APIs/Ruby/Ruby-1.8.7.bas</Other>