Sun, 16 May 2021 20:07:24 +0200
Replaced all imports of PyQt5 to PyQt6 and started to replace code using obsoleted methods and adapt to the PyQt6 enum usage.
# -*- coding: utf-8 -*- # Copyright (c) 2019 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the pip packages management widget. """ import textwrap import os import html.parser import contextlib from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QUrlQuery from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from PyQt6.QtWidgets import ( QWidget, QToolButton, QApplication, QHeaderView, QTreeWidgetItem, QMenu, QDialog ) from E5Gui.E5Application import e5App from E5Gui import E5MessageBox from E5Gui.E5OverrideCursor import E5OverrideCursor from .Ui_PipPackagesWidget import Ui_PipPackagesWidget import UI.PixmapCache import Globals import Preferences class PypiSearchResultsParser(html.parser.HTMLParser): """ Class implementing the parser for the PyPI search result page. """ ClassPrefix = "package-snippet__" def __init__(self, data): """ Constructor @param data data to be parsed @type str """ super().__init__() self.__results = [] self.__activeClass = None self.feed(data) def __getClass(self, attrs): """ Private method to extract the class attribute out of the list of attributes. @param attrs list of tag attributes as (name, value) tuples @type list of tuple of (str, str) @return value of the 'class' attribute or None @rtype str """ for name, value in attrs: if name == "class": return value return None def __getDate(self, attrs): """ Private method to extract the datetime attribute out of the list of attributes and process it. @param attrs list of tag attributes as (name, value) tuples @type list of tuple of (str, str) @return value of the 'class' attribute or None @rtype str """ for name, value in attrs: if name == "datetime": return value.split("T")[0] return None def handle_starttag(self, tag, attrs): """ Public method to process the start tag. @param tag tag name (all lowercase) @type str @param attrs list of tag attributes as (name, value) tuples @type list of tuple of (str, str) """ if tag == "a" and self.__getClass(attrs) == "package-snippet": self.__results.append({}) if tag in ("span", "p"): tagClass = self.__getClass(attrs) if tagClass in ( "package-snippet__name", "package-snippet__description", "package-snippet__version", "package-snippet__released", ): self.__activeClass = tagClass else: self.__activeClass = None elif tag == "time": attributeName = self.__activeClass.replace(self.ClassPrefix, "") self.__results[-1][attributeName] = self.__getDate(attrs) self.__activeClass = None else: self.__activeClass = None def handle_data(self, data): """ Public method process arbitrary data. @param data data to be processed @type str """ if self.__activeClass is not None: attributeName = self.__activeClass.replace(self.ClassPrefix, "") self.__results[-1][attributeName] = data def handle_endtag(self, tag): """ Public method to process the end tag. @param tag tag name (all lowercase) @type str """ self.__activeClass = None def getResults(self): """ Public method to get the extracted search results. @return extracted result data @rtype list of dict """ return self.__results class PipPackagesWidget(QWidget, Ui_PipPackagesWidget): """ Class implementing the pip packages management widget. """ ShowProcessGeneralMode = 0 ShowProcessClassifiersMode = 1 ShowProcessEntryPointsMode = 2 ShowProcessFilesListMode = 3 SearchVersionRole = Qt.ItemDataRole.UserRole + 1 def __init__(self, pip, parent=None): """ Constructor @param pip reference to the global pip interface @type Pip @param parent reference to the parent widget @type QWidget """ super().__init__(parent) self.setupUi(self) self.pipMenuButton.setObjectName( "pip_supermenu_button") self.pipMenuButton.setIcon(UI.PixmapCache.getIcon("superMenu")) self.pipMenuButton.setToolTip(self.tr("pip Menu")) self.pipMenuButton.setPopupMode( QToolButton.ToolButtonPopupMode.InstantPopup) self.pipMenuButton.setToolButtonStyle( Qt.ToolButtonStyle.ToolButtonIconOnly) self.pipMenuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.pipMenuButton.setAutoRaise(True) self.pipMenuButton.setShowMenuInside(True) self.refreshButton.setIcon(UI.PixmapCache.getIcon("reload")) self.upgradeButton.setIcon(UI.PixmapCache.getIcon("1uparrow")) self.upgradeAllButton.setIcon(UI.PixmapCache.getIcon("2uparrow")) self.uninstallButton.setIcon(UI.PixmapCache.getIcon("minus")) self.showPackageDetailsButton.setIcon(UI.PixmapCache.getIcon("info")) self.searchToggleButton.setIcon(UI.PixmapCache.getIcon("find")) self.searchButton.setIcon(UI.PixmapCache.getIcon("findNext")) self.installButton.setIcon(UI.PixmapCache.getIcon("plus")) self.installUserSiteButton.setIcon(UI.PixmapCache.getIcon("addUser")) self.showDetailsButton.setIcon(UI.PixmapCache.getIcon("info")) self.__pip = pip self.packagesList.header().setSortIndicator( 0, Qt.SortOrder.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() self.__queryName = [] self.__querySummary = [] self.__replies = [] self.__packageDetailsDialog = None def __populateEnvironments(self): """ Private method to get a list of environments and populate the selector. """ self.environmentsComboBox.addItem("") projectVenv = self.__pip.getProjectEnvironmentString() if projectVenv: self.environmentsComboBox.addItem(projectVenv) self.environmentsComboBox.addItems( self.__pip.getVirtualenvNames( noRemote=True, noConda=Preferences.getPip("ExcludeCondaEnvironments") ) ) def __isPipAvailable(self): """ Private method to check, if the pip package is available for the selected environment. @return flag indicating availability @rtype bool """ available = False venvName = self.environmentsComboBox.currentText() if venvName: available = ( len(self.packagesList.findItems( "pip", Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive)) == 1 ) return available def __availablePipVersion(self): """ Private method to get the pip version of the selected environment. @return tuple containing the version number or tuple with all zeros in case pip is not available @rtype tuple of int """ pipVersionTuple = (0, 0, 0) venvName = self.environmentsComboBox.currentText() if venvName: pipList = self.packagesList.findItems( "pip", Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive ) if len(pipList) > 0: pipVersionTuple = Globals.versionToTuple(pipList[0].text(1)) return pipVersionTuple def getPip(self): """ Public method to get a reference to the pip interface object. @return reference to the pip interface object @rtype Pip """ return self.__pip ####################################################################### ## 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. """ if self.__isPipAvailable(): self.upgradeButton.setEnabled( bool(self.__selectedUpdateableItems())) self.uninstallButton.setEnabled( bool(self.packagesList.selectedItems())) self.upgradeAllButton.setEnabled( bool(self.__allUpdateableItems())) self.showPackageDetailsButton.setEnabled( len(self.packagesList.selectedItems()) == 1) else: self.upgradeButton.setEnabled(False) self.uninstallButton.setEnabled(False) self.upgradeAllButton.setEnabled(False) self.showPackageDetailsButton.setEnabled(False) def __refreshPackagesList(self): """ Private method to referesh the packages list. """ self.packagesList.clear() venvName = self.environmentsComboBox.currentText() if venvName: interpreter = self.__pip.getVirtualenvInterpreter(venvName) if interpreter: self.statusLabel.show() self.statusLabel.setText( self.tr("Getting installed packages...")) with E5OverrideCursor(): # 1. populate with installed packages 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 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.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive ) if items: itm = items[0] itm.setText(2, latest) self.packagesList.sortItems(0, Qt.SortOrder.AscendingOrder) for col in range(self.packagesList.columnCount()): self.packagesList.resizeColumnToContents(col) self.packagesList.setUpdatesEnabled(True) self.statusLabel.hide() self.__updateActionButtons() self.__updateSearchActionButtons() self.__updateSearchButton() @pyqtSlot(int) def on_environmentsComboBox_currentIndexChanged(self, index): """ Private slot handling the selection of a Python environment. @param index index of the selected Python 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 args = ["-m", "pip", "show"] if self.verboseCheckBox.isChecked(): args.append("--verbose") if self.installedFilesCheckBox.isChecked(): args.append("--files") args.append(itm.text(0)) with E5OverrideCursor(): 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.ResizeMode.ResizeToContents) if ( header.sectionSize(0) + header.sectionSize(1) < header.width() ): header.setStretchLastSection(True) self.__updateActionButtons() @pyqtSlot(QTreeWidgetItem, int) def on_packagesList_itemActivated(self, item, column): """ Private slot reacting on a package item activation. @param item reference to the activated item @type QTreeWidgetItem @param column activated column @type int """ packageName = item.text(0) upgradable = bool(item.text(2)) if column == 1: # show details for installed version packageVersion = item.text(1) else: # show details for available version or installed one if item.text(2): packageVersion = item.text(2) else: packageVersion = item.text(1) self.__showPackageDetails(packageName, packageVersion, upgradable=upgradable) @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() with E5OverrideCursor(): self.__populateEnvironments() index = self.environmentsComboBox.findText( currentEnvironment, Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive ) if index != -1: self.environmentsComboBox.setCurrentIndex(index) 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: self.executeUpgradePackages(packages) @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: self.executeUpgradePackages(packages) @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()] self.executeUninstallPackages(packages) def executeUninstallPackages(self, packages): """ Public method to uninstall the given list of packages. @param packages list of package names to be uninstalled @type list of str """ if packages: ok = self.__pip.uninstallPackages( packages, venvName=self.environmentsComboBox.currentText()) if ok: self.on_refreshButton_clicked() def executeUpgradePackages(self, packages): """ Public method to execute the pip upgrade command. @param packages list of package names to be upgraded @type list of str """ ok = self.__pip.upgradePackages( packages, venvName=self.environmentsComboBox.currentText(), userSite=self.userCheckBox.isChecked()) if ok: self.on_refreshButton_clicked() @pyqtSlot() def on_showPackageDetailsButton_clicked(self): """ Private slot to show information for the selected package. """ item = self.packagesList.selectedItems()[0] if item: packageName = item.text(0) upgradable = bool(item.text(2)) # show details for available version or installed one if item.text(2): packageVersion = item.text(2) else: packageVersion = item.text(1) self.__showPackageDetails(packageName, packageVersion, upgradable=upgradable) ####################################################################### ## Search widget related methods below ####################################################################### def __updateSearchActionButtons(self): """ Private method to update the action button states of the search widget. """ installEnable = ( len(self.searchResultList.selectedItems()) > 0 and self.environmentsComboBox.currentIndex() > 0 and self.__isPipAvailable() ) self.installButton.setEnabled(installEnable) self.installUserSiteButton.setEnabled(installEnable) self.showDetailsButton.setEnabled( len(self.searchResultList.selectedItems()) == 1 and self.__isPipAvailable() ) def __updateSearchButton(self): """ Private method to update the state of the search button. """ self.searchButton.setEnabled( bool(self.searchEditName.text()) and self.__isPipAvailable() ) @pyqtSlot(bool) def on_searchToggleButton_toggled(self, checked): """ Private slot to togle the search widget. @param checked state of the search widget button @type bool """ self.searchWidget.setVisible(checked) if checked: self.searchEditName.setFocus(Qt.FocusReason.OtherFocusReason) self.searchEditName.selectAll() self.__updateSearchActionButtons() self.__updateSearchButton() @pyqtSlot(str) def on_searchEditName_textChanged(self, txt): """ Private slot handling a change of the search term. @param txt search term @type str """ self.__updateSearchButton() @pyqtSlot() def on_searchEditName_returnPressed(self): """ Private slot initiating a search via a press of the Return key. """ if ( bool(self.searchEditName.text()) and self.__isPipAvailable() ): self.__search() @pyqtSlot() def on_searchButton_clicked(self): """ Private slot handling a press of the search button. """ self.__search() @pyqtSlot() def on_searchResultList_itemSelectionChanged(self): """ Private slot handling changes of the search result selection. """ self.__updateSearchActionButtons() def __search(self): """ Private method to perform the search by calling the PyPI search URL. """ self.searchResultList.clear() self.searchInfoLabel.clear() self.searchButton.setEnabled(False) searchTerm = self.searchEditName.text().strip() searchTerm = bytes(QUrl.toPercentEncoding(searchTerm)).decode() urlQuery = QUrlQuery() urlQuery.addQueryItem("q", searchTerm) url = QUrl(self.__pip.getIndexUrlSearch()) url.setQuery(urlQuery) request = QNetworkRequest(QUrl(url)) request.setAttribute( QNetworkRequest.Attribute.CacheLoadControlAttribute, QNetworkRequest.CacheLoadControl.AlwaysNetwork) reply = self.__pip.getNetworkAccessManager().get(request) reply.finished.connect( lambda: self.__searchResponse(reply)) self.__replies.append(reply) def __searchResponse(self, reply): """ Private method to extract the search result data from the response. @param reply reference to the reply object containing the data @type QNetworkReply """ if reply in self.__replies: self.__replies.remove(reply) urlQuery = QUrlQuery(reply.url()) searchTerm = urlQuery.queryItemValue("q") if reply.error() != QNetworkReply.NetworkError.NoError: E5MessageBox.warning( None, self.tr("Search PyPI"), self.tr( "<p>Received an error while searching for <b>{0}</b>.</p>" "<p>Error: {1}</p>" ).format(searchTerm, reply.errorString()) ) reply.deleteLater() return data = bytes(reply.readAll()).decode() reply.deleteLater() results = PypiSearchResultsParser(data).getResults() if results: if len(results) < 20: msg = self.tr("%n package(s) found.", "", len(results)) else: msg = self.tr("Showing first 20 packages found.") self.searchInfoLabel.setText(msg) else: E5MessageBox.warning( self, self.tr("Search PyPI"), self.tr("""<p>There were no results for <b>{0}</b>.</p>""")) self.searchInfoLabel.setText( self.tr("""<p>There were no results for <b>{0}</b>.</p>""")) wrapper = textwrap.TextWrapper(width=80) for result in results: try: description = "\n".join([ wrapper.fill(line) for line in result['description'].strip().splitlines() ]) except KeyError: description = "" itm = QTreeWidgetItem( self.searchResultList, [ result['name'].strip(), result['version'], result["released"].strip(), description, ]) itm.setData(0, self.SearchVersionRole, result['version']) header = self.searchResultList.header() header.setStretchLastSection(False) header.resizeSections(QHeaderView.ResizeMode.ResizeToContents) headerSize = 0 for col in range(header.count()): headerSize += header.sectionSize(col) if headerSize < header.width(): header.setStretchLastSection(True) self.__finishSearch() def __finishSearch(self): """ Private slot performing the search finishing actions. """ self.__updateSearchActionButtons() self.__updateSearchButton() self.searchEditName.setFocus(Qt.FocusReason.OtherFocusReason) @pyqtSlot() def on_installButton_clicked(self): """ Private slot to handle pressing the Install button.. """ packages = [ itm.text(0).strip() for itm in self.searchResultList.selectedItems() ] self.executeInstallPackages(packages) @pyqtSlot() def on_installUserSiteButton_clicked(self): """ Private slot to handle pressing the Install to User-Site button.. """ packages = [ itm.text(0).strip() for itm in self.searchResultList.selectedItems() ] self.executeInstallPackages(packages, userSite=True) def executeInstallPackages(self, packages, userSite=False): """ Public method to install the given list of packages. @param packages list of package names to be installed @type list of str @param userSite flag indicating to install to the user directory @type bool """ venvName = self.environmentsComboBox.currentText() if venvName and packages: self.__pip.installPackages(packages, venvName=venvName, userSite=userSite) self.on_refreshButton_clicked() @pyqtSlot() def on_showDetailsButton_clicked(self): """ Private slot to handle pressing the Show Details button. """ self.__showSearchedDetails() @pyqtSlot(QTreeWidgetItem, int) def on_searchResultList_itemActivated(self, item, column): """ Private slot reacting on an search result item activation. @param item reference to the activated item @type QTreeWidgetItem @param column activated column @type int """ self.__showSearchedDetails(item) def __showSearchedDetails(self, item=None): """ Private slot to show details about the selected search result package. @param item reference to the search result item to show details for @type QTreeWidgetItem """ self.showDetailsButton.setEnabled(False) if not item: item = self.searchResultList.selectedItems()[0] packageVersion = item.data(0, self.SearchVersionRole) packageName = item.text(0) self.__showPackageDetails(packageName, packageVersion, installable=True) def __showPackageDetails(self, packageName, packageVersion, upgradable=False, installable=False): """ Private method to populate the package details dialog. @param packageName name of the package to show details for @type str @param packageVersion version of the package @type str @param upgradable flag indicating that the package may be upgraded (defaults to False) @type bool (optional) @param installable flag indicating that the package may be installed (defaults to False) @type bool (optional) """ with E5OverrideCursor(): packageData = self.__pip.getPackageDetails( packageName, packageVersion) if packageData: from .PipPackageDetailsDialog import PipPackageDetailsDialog self.showDetailsButton.setEnabled(True) if installable: buttonsMode = PipPackageDetailsDialog.ButtonInstall elif upgradable: buttonsMode = ( PipPackageDetailsDialog.ButtonRemove | PipPackageDetailsDialog.ButtonUpgrade ) else: buttonsMode = PipPackageDetailsDialog.ButtonRemove if self.__packageDetailsDialog is not None: self.__packageDetailsDialog.close() self.__packageDetailsDialog = ( PipPackageDetailsDialog(packageData, buttonsMode=buttonsMode, parent=self) ) self.__packageDetailsDialog.show() else: E5MessageBox.warning( self, self.tr("Search PyPI"), self.tr("""<p>No package details info for <b>{0}</b>""" """ available.</p>""").format(packageName)) ####################################################################### ## Menu related methods below ####################################################################### def __initPipMenu(self): """ Private method to create the super menu and attach it to the super menu button. """ self.__pipMenu = QMenu() self.__installPipAct = self.__pipMenu.addAction( self.tr("Install Pip"), self.__installPip) self.__installPipUserAct = self.__pipMenu.addAction( self.tr("Install Pip to User-Site"), self.__installPipUser) self.__repairPipAct = self.__pipMenu.addAction( self.tr("Repair Pip"), self.__repairPip) self.__pipMenu.addSeparator() self.__installPackagesAct = self.__pipMenu.addAction( self.tr("Install Packages"), self.__installPackages) self.__installLocalPackageAct = self.__pipMenu.addAction( self.tr("Install Local Package"), self.__installLocalPackage) self.__pipMenu.addSeparator() self.__installRequirementsAct = self.__pipMenu.addAction( self.tr("Install Requirements"), self.__installRequirements) self.__reinstallPackagesAct = self.__pipMenu.addAction( self.tr("Re-Install Selected Packages"), self.__reinstallPackages) self.__uninstallRequirementsAct = self.__pipMenu.addAction( self.tr("Uninstall Requirements"), self.__uninstallRequirements) self.__generateRequirementsAct = self.__pipMenu.addAction( self.tr("Generate Requirements..."), self.__generateRequirements) self.__pipMenu.addSeparator() self.__cacheInfoAct = self.__pipMenu.addAction( self.tr("Show Cache Info..."), self.__showCacheInfo) self.__cacheShowListAct = self.__pipMenu.addAction( self.tr("Show Cached Files..."), self.__showCacheList) self.__cacheRemoveAct = self.__pipMenu.addAction( self.tr("Remove Cached Files..."), self.__removeCachedFiles) self.__cachePurgeAct = self.__pipMenu.addAction( self.tr("Purge Cache..."), self.__purgeCache) self.__pipMenu.addSeparator() # editUserConfigAct self.__pipMenu.addAction( self.tr("Edit User Configuration..."), self.__editUserConfiguration) self.__editVirtualenvConfigAct = self.__pipMenu.addAction( self.tr("Edit Environment Configuration..."), self.__editVirtualenvConfiguration) self.__pipMenu.addSeparator() # pipConfigAct self.__pipMenu.addAction( self.tr("Configure..."), self.__pipConfigure) self.__pipMenu.aboutToShow.connect(self.__aboutToShowPipMenu) self.pipMenuButton.setMenu(self.__pipMenu) def __aboutToShowPipMenu(self): """ Private slot to set the action enabled status. """ enable = bool(self.environmentsComboBox.currentText()) enablePip = self.__isPipAvailable() enablePipCache = self.__availablePipVersion() >= (20, 1, 0) self.__installPipAct.setEnabled(not enablePip) self.__installPipUserAct.setEnabled(not enablePip) self.__repairPipAct.setEnabled(enablePip) self.__installPackagesAct.setEnabled(enablePip) self.__installLocalPackageAct.setEnabled(enablePip) self.__reinstallPackagesAct.setEnabled(enablePip) self.__installRequirementsAct.setEnabled(enablePip) self.__uninstallRequirementsAct.setEnabled(enablePip) self.__generateRequirementsAct.setEnabled(enablePip) self.__cacheInfoAct.setEnabled(enablePipCache) self.__cacheShowListAct.setEnabled(enablePipCache) self.__cacheRemoveAct.setEnabled(enablePipCache) self.__cachePurgeAct.setEnabled(enablePipCache) self.__editVirtualenvConfigAct.setEnabled(enable) @pyqtSlot() def __installPip(self): """ Private slot to install pip into the selected environment. """ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.installPip(venvName) self.on_refreshButton_clicked() @pyqtSlot() def __installPipUser(self): """ Private slot to install pip into the user site for the selected environment. """ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.installPip(venvName, userSite=True) self.on_refreshButton_clicked() @pyqtSlot() def __repairPip(self): """ Private slot to repair the pip installation of the selected environment. """ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.repairPip(venvName) self.on_refreshButton_clicked() @pyqtSlot() def __installPackages(self): """ Private slot to install packages to be given by the user. """ venvName = self.environmentsComboBox.currentText() if venvName: from .PipPackagesInputDialog import PipPackagesInputDialog dlg = PipPackagesInputDialog(self, self.tr("Install Packages")) if dlg.exec() == QDialog.DialogCode.Accepted: packages, user = dlg.getData() self.executeInstallPackages(packages, userSite=user) @pyqtSlot() def __installLocalPackage(self): """ Private slot to install a package available on local storage. """ venvName = self.environmentsComboBox.currentText() if venvName: from .PipFileSelectionDialog import PipFileSelectionDialog dlg = PipFileSelectionDialog(self, "package") if dlg.exec() == QDialog.DialogCode.Accepted: package, user = dlg.getData() if package and os.path.exists(package): self.executeInstallPackages([package], userSite=user) @pyqtSlot() def __reinstallPackages(self): """ Private slot to force a re-installation of the selected packages. """ packages = [itm.text(0) for itm in self.packagesList.selectedItems()] venvName = self.environmentsComboBox.currentText() if venvName and packages: self.__pip.installPackages(packages, venvName=venvName, forceReinstall=True) self.on_refreshButton_clicked() @pyqtSlot() def __installRequirements(self): """ Private slot to install packages as given in a requirements file. """ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.installRequirements(venvName) self.on_refreshButton_clicked() @pyqtSlot() def __uninstallRequirements(self): """ Private slot to uninstall packages as given in a requirements file. """ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.uninstallRequirements(venvName) self.on_refreshButton_clicked() @pyqtSlot() def __generateRequirements(self): """ Private slot to generate the contents for a requirements file. """ venvName = self.environmentsComboBox.currentText() if venvName: from .PipFreezeDialog import PipFreezeDialog self.__freezeDialog = PipFreezeDialog(self.__pip, self) self.__freezeDialog.show() self.__freezeDialog.start(venvName) @pyqtSlot() def __editUserConfiguration(self): """ Private slot to edit the user configuration. """ self.__editConfiguration() @pyqtSlot() def __editVirtualenvConfiguration(self): """ Private slot to edit the configuration of the selected environment. """ venvName = self.environmentsComboBox.currentText() if venvName: self.__editConfiguration(venvName=venvName) def __editConfiguration(self, venvName=""): """ Private method to edit a configuration. @param venvName name of the environment to act upon @type str """ from QScintilla.MiniEditor import MiniEditor if venvName: cfgFile = self.__pip.getVirtualenvConfig(venvName) if not cfgFile: return else: cfgFile = self.__pip.getUserConfig() cfgDir = os.path.dirname(cfgFile) if not cfgDir: E5MessageBox.critical( None, self.tr("Edit Configuration"), self.tr("""No valid configuration path determined.""" """ Aborting""")) return try: if not os.path.isdir(cfgDir): os.makedirs(cfgDir) except OSError: E5MessageBox.critical( None, self.tr("Edit Configuration"), self.tr("""No valid configuration path determined.""" """ Aborting""")) return if not os.path.exists(cfgFile): with contextlib.suppress(OSError), open(cfgFile, "w") as f: f.write("[global]\n") # check, if the destination is writeable if not os.access(cfgFile, os.W_OK): E5MessageBox.critical( None, self.tr("Edit Configuration"), self.tr("""No valid configuration path determined.""" """ Aborting""")) return self.__editor = MiniEditor(cfgFile, "Properties") self.__editor.show() def __pipConfigure(self): """ Private slot to open the configuration page. """ e5App().getObject("UserInterface").showPreferences("pipPage") @pyqtSlot() def __showCacheInfo(self): """ Private slot to show information about the cache. """ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.showCacheInfo(venvName) @pyqtSlot() def __showCacheList(self): """ Private slot to show a list of cached files. """ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.cacheList(venvName) @pyqtSlot() def __removeCachedFiles(self): """ Private slot to remove files from the pip cache. """ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.cacheRemove(venvName) @pyqtSlot() def __purgeCache(self): """ Private slot to empty the pip cache. """ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.cachePurge(venvName)