diff -r e9e7eca7efee -r bf71ee032bb4 src/eric7/PipInterface/PipPackagesWidget.py --- a/src/eric7/PipInterface/PipPackagesWidget.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/PipInterface/PipPackagesWidget.py Wed Jul 13 14:55:47 2022 +0200 @@ -18,8 +18,14 @@ from PyQt6.QtGui import QIcon from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from PyQt6.QtWidgets import ( - QWidget, QToolButton, QApplication, QHeaderView, QTreeWidgetItem, - QMenu, QDialog, QAbstractItemView + QWidget, + QToolButton, + QApplication, + QHeaderView, + QTreeWidgetItem, + QMenu, + QDialog, + QAbstractItemView, ) from EricWidgets.EricApplication import ericApp @@ -38,12 +44,13 @@ """ 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 """ @@ -51,12 +58,12 @@ 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 @@ -65,14 +72,14 @@ 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 @@ -81,13 +88,13 @@ 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 @@ -95,12 +102,14 @@ """ 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", + "package-snippet__name", + "package-snippet__description", + "package-snippet__version", + "package-snippet__released", "package-snippet__created", ): self.__activeClass = tagClass @@ -112,31 +121,31 @@ 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 """ @@ -147,27 +156,28 @@ """ Class implementing the pip packages management widget. """ + ShowProcessGeneralMode = 0 ShowProcessClassifiersMode = 1 ShowProcessEntryPointsMode = 2 ShowProcessFilesListMode = 3 - + SearchVersionRole = Qt.ItemDataRole.UserRole + 1 VulnerabilityRole = Qt.ItemDataRole.UserRole + 2 - + PackageColumn = 0 InstalledVersionColumn = 1 AvailableVersionColumn = 2 VulnerabilityColumn = 3 - + DepPackageColumn = 0 DepInstalledVersionColumn = 1 DepRequiredVersionColumn = 2 - + def __init__(self, pip, parent=None): """ Constructor - + @param pip reference to the global pip interface @type Pip @param parent reference to the parent widget @@ -175,58 +185,44 @@ """ super().__init__(parent) self.setupUi(self) - + self.layout().setContentsMargins(0, 3, 0, 0) - + self.viewToggleButton.setIcon(UI.PixmapCache.getIcon("viewListTree")) - - self.pipMenuButton.setObjectName( - "pip_supermenu_button") + + 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.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.searchMoreButton.setIcon( - UI.PixmapCache.getIcon("plus")) - self.installButton.setIcon( - UI.PixmapCache.getIcon("plus")) - self.installUserSiteButton.setIcon( - UI.PixmapCache.getIcon("addUser")) - self.showDetailsButton.setIcon( - UI.PixmapCache.getIcon("info")) - - self.refreshDependenciesButton.setIcon( - UI.PixmapCache.getIcon("reload")) - self.showDepPackageDetailsButton.setIcon( - UI.PixmapCache.getIcon("info")) - + + 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.searchMoreButton.setIcon(UI.PixmapCache.getIcon("plus")) + self.installButton.setIcon(UI.PixmapCache.getIcon("plus")) + self.installUserSiteButton.setIcon(UI.PixmapCache.getIcon("addUser")) + self.showDetailsButton.setIcon(UI.PixmapCache.getIcon("info")) + + self.refreshDependenciesButton.setIcon(UI.PixmapCache.getIcon("reload")) + self.showDepPackageDetailsButton.setIcon(UI.PixmapCache.getIcon("info")) + self.__pip = pip - + self.packagesList.header().setSortIndicator( - PipPackagesWidget.PackageColumn, Qt.SortOrder.AscendingOrder) + PipPackagesWidget.PackageColumn, Qt.SortOrder.AscendingOrder + ) self.dependenciesList.header().setSortIndicator( - PipPackagesWidget.DepPackageColumn, Qt.SortOrder.AscendingOrder) - + PipPackagesWidget.DepPackageColumn, Qt.SortOrder.AscendingOrder + ) + self.__infoLabels = { "name": self.tr("Name:"), "version": self.tr("Version:"), @@ -245,38 +241,34 @@ } self.infoWidget.setHeaderLabels(["Key", "Value"]) self.dependencyInfoWidget.setHeaderLabels(["Key", "Value"]) - + venvManager = ericApp().getObject("VirtualEnvManager") - venvManager.virtualEnvironmentAdded.connect( - self.on_refreshButton_clicked) - venvManager.virtualEnvironmentRemoved.connect( - self.on_refreshButton_clicked) + venvManager.virtualEnvironmentAdded.connect(self.on_refreshButton_clicked) + venvManager.virtualEnvironmentRemoved.connect(self.on_refreshButton_clicked) self.__selectedEnvironment = None - + project = ericApp().getObject("Project") - project.projectOpened.connect( - self.__projectOpened) - project.projectClosed.connect( - self.__projectClosed) - + project.projectOpened.connect(self.__projectOpened) + project.projectClosed.connect(self.__projectClosed) + self.__initPipMenu() self.__populateEnvironments() self.__updateActionButtons() self.__updateDepActionButtons() - + self.statusLabel.hide() self.searchWidget.hide() self.__lastSearchPage = 0 - + self.__queryName = [] self.__querySummary = [] - + self.__replies = [] - + self.__packageDetailsDialog = None - + self.viewsStackWidget.setCurrentWidget(self.packagesPage) - + @pyqtSlot() def __projectOpened(self): """ @@ -285,19 +277,19 @@ projectVenv = self.__pip.getProjectEnvironmentString() if projectVenv: self.environmentsComboBox.insertItem(1, projectVenv) - + @pyqtSlot(bool) def __projectClosed(self, shutdown): """ Private slot to handle the projectClosed signal. - + @param shutdown flag indicating the IDE shutdown @type bool """ if not shutdown: # the project entry is always at index 1 self.environmentsComboBox.removeItem(1) - + def __populateEnvironments(self): """ Private method to get a list of environments and populate the selector. @@ -308,36 +300,38 @@ self.environmentsComboBox.addItem(projectVenv) self.environmentsComboBox.addItems( self.__pip.getVirtualenvNames( - noRemote=True, - noConda=Preferences.getPip("ExcludeCondaEnvironments") + 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 + 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 @@ -346,44 +340,45 @@ venvName = self.environmentsComboBox.currentText() if venvName: pipList = self.packagesList.findItems( - "pip", - Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive + "pip", Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive ) if len(pipList) > 0: pipVersionTuple = Globals.versionToTuple( - pipList[0].text(PipPackagesWidget.InstalledVersionColumn)) - + pipList[0].text(PipPackagesWidget.InstalledVersionColumn) + ) + 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() + itm + for itm in self.packagesList.selectedItems() if bool(itm.text(PipPackagesWidget.AvailableVersionColumn)) ] - + 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 """ @@ -392,28 +387,26 @@ itm = self.packagesList.topLevelItem(index) if itm.text(PipPackagesWidget.AvailableVersionColumn): 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.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) + 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 refresh the packages list. @@ -424,9 +417,8 @@ interpreter = self.__pip.getVirtualenvInterpreter(venvName) if interpreter: self.statusLabel.show() - self.statusLabel.setText( - self.tr("Getting installed packages...")) - + self.statusLabel.setText(self.tr("Getting installed packages...")) + with EricOverrideCursor(): # 1. populate with installed packages self.packagesList.setUpdatesEnabled(False) @@ -437,13 +429,11 @@ usersite=self.userCheckBox.isChecked(), ) for package, version in installedPackages: - QTreeWidgetItem(self.packagesList, - [package, version, "", ""]) + QTreeWidgetItem(self.packagesList, [package, version, "", ""]) self.packagesList.setUpdatesEnabled(True) - self.statusLabel.setText( - self.tr("Getting outdated packages...")) + self.statusLabel.setText(self.tr("Getting outdated packages...")) QApplication.processEvents() - + # 2. update with update information self.packagesList.setUpdatesEnabled(False) outdatedPackages = self.__pip.getOutdatedPackages( @@ -455,37 +445,36 @@ for package, _version, latest in outdatedPackages: items = self.packagesList.findItems( package, - Qt.MatchFlag.MatchExactly | - Qt.MatchFlag.MatchCaseSensitive + Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive, ) if items: itm = items[0] itm.setText( - PipPackagesWidget.AvailableVersionColumn, - latest) - + PipPackagesWidget.AvailableVersionColumn, latest + ) + self.packagesList.sortItems( - PipPackagesWidget.PackageColumn, - Qt.SortOrder.AscendingOrder) + PipPackagesWidget.PackageColumn, Qt.SortOrder.AscendingOrder + ) for col in range(self.packagesList.columnCount()): self.packagesList.resizeColumnToContents(col) self.packagesList.setUpdatesEnabled(True) - + # 3. update with vulnerability information if self.vulnerabilityCheckBox.isChecked(): self.__updateVulnerabilityData() self.statusLabel.hide() - + self.__updateActionButtons() self.__updateSearchActionButtons() self.__updateSearchButton() self.__updateSearchMoreButton(False) - + @pyqtSlot(str) def on_environmentsComboBox_currentTextChanged(self, name): """ Private slot handling the selection of a Python environment. - + @param name name of the selected Python environment @type str """ @@ -495,32 +484,32 @@ else: self.__refreshPackagesList() self.__selectedEnvironment = name - + @pyqtSlot() def on_localCheckBox_clicked(self): """ Private slot handling the switching of the local mode. """ self.__refreshPackagesList() - + @pyqtSlot() def on_notRequiredCheckBox_clicked(self): """ Private slot handling the switching of the 'not required' mode. """ self.__refreshPackagesList() - + @pyqtSlot() def on_userCheckBox_clicked(self): """ Private slot handling the switching of the 'user-site' mode. """ self.__refreshPackagesList() - + def __showPackageInformation(self, packageName, infoWidget): """ Private method to show information for a package. - + @param packageName name of the package @type str @param infoWidget reference to the widget to contain the information @@ -530,17 +519,17 @@ 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(packageName) - + with EricOverrideCursor(): success, output = self.__pip.runProcess(args, interpreter) - + if success and output: mode = self.ShowProcessGeneralMode for line in output.splitlines(): @@ -548,9 +537,7 @@ if line != "---": if mode != self.ShowProcessGeneralMode: if line[0] == " ": - QTreeWidgetItem( - infoWidget, - [" ", line.strip()]) + QTreeWidgetItem(infoWidget, [" ", line.strip()]) else: mode = self.ShowProcessGeneralMode if mode == self.ShowProcessGeneralMode: @@ -562,8 +549,8 @@ label = label.lower() if label in self.__infoLabels: QTreeWidgetItem( - infoWidget, - [self.__infoLabels[label], info]) + infoWidget, [self.__infoLabels[label], info] + ) if label == "files": mode = self.ShowProcessFilesListMode elif label == "classifiers": @@ -571,16 +558,13 @@ elif label == "entry-points": mode = self.ShowProcessEntryPointsMode infoWidget.scrollToTop() - + header = infoWidget.header() header.setStretchLastSection(False) header.resizeSections(QHeaderView.ResizeMode.ResizeToContents) - if ( - header.sectionSize(0) + header.sectionSize(1) < - header.width() - ): + if header.sectionSize(0) + header.sectionSize(1) < header.width(): header.setStretchLastSection(True) - + @pyqtSlot() def on_packagesList_itemSelectionChanged(self): """ @@ -588,43 +572,43 @@ """ if len(self.packagesList.selectedItems()) == 0: self.infoWidget.clear() - + @pyqtSlot(QTreeWidgetItem, int) def on_packagesList_itemPressed(self, item, column): """ Private slot reacting on a package item being pressed. - + @param item reference to the pressed item @type QTreeWidgetItem @param column pressed column @type int """ self.infoWidget.clear() - + if item is not None: - if ( - column == PipPackagesWidget.VulnerabilityColumn and - bool(item.text(PipPackagesWidget.VulnerabilityColumn)) + if column == PipPackagesWidget.VulnerabilityColumn and bool( + item.text(PipPackagesWidget.VulnerabilityColumn) ): self.__showVulnerabilityInformation( item.text(PipPackagesWidget.PackageColumn), item.text(PipPackagesWidget.InstalledVersionColumn), - item.data(PipPackagesWidget.VulnerabilityColumn, - PipPackagesWidget.VulnerabilityRole) + item.data( + PipPackagesWidget.VulnerabilityColumn, + PipPackagesWidget.VulnerabilityRole, + ), ) else: self.__showPackageInformation( - item.text(PipPackagesWidget.PackageColumn), - self.infoWidget + item.text(PipPackagesWidget.PackageColumn), self.infoWidget ) - + self.__updateActionButtons() - + @pyqtSlot(QTreeWidgetItem, int) def on_packagesList_itemActivated(self, item, column): """ Private slot reacting on a package item being activated. - + @param item reference to the activated item @type QTreeWidgetItem @param column activated column @@ -634,44 +618,42 @@ upgradable = bool(item.text(PipPackagesWidget.AvailableVersionColumn)) if column == PipPackagesWidget.InstalledVersionColumn: # show details for installed version - packageVersion = item.text( - PipPackagesWidget.InstalledVersionColumn) + packageVersion = item.text(PipPackagesWidget.InstalledVersionColumn) else: # show details for available version or installed one if item.text(PipPackagesWidget.AvailableVersionColumn): - packageVersion = item.text( - PipPackagesWidget.AvailableVersionColumn) + packageVersion = item.text(PipPackagesWidget.AvailableVersionColumn) else: - packageVersion = item.text( - PipPackagesWidget.InstalledVersionColumn) - - self.__showPackageDetails(packageName, packageVersion, - upgradable=upgradable) - + packageVersion = item.text(PipPackagesWidget.InstalledVersionColumn) + + 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_itemPressed(self.packagesList.currentItem(), - self.packagesList.currentColumn()) - + self.on_packagesList_itemPressed( + self.packagesList.currentItem(), self.packagesList.currentColumn() + ) + @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_itemPressed(self.packagesList.currentItem(), - self.packagesList.currentColumn()) - + self.on_packagesList_itemPressed( + self.packagesList.currentItem(), self.packagesList.currentColumn() + ) + @pyqtSlot() def on_refreshButton_clicked(self): """ @@ -680,75 +662,83 @@ currentEnvironment = self.environmentsComboBox.currentText() self.environmentsComboBox.clear() self.packagesList.clear() - + with EricOverrideCursor(): self.__populateEnvironments() - + index = self.environmentsComboBox.findText( currentEnvironment, - Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive + 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(PipPackagesWidget.PackageColumn) - for itm in self.__selectedUpdateableItems()] + packages = [ + itm.text(PipPackagesWidget.PackageColumn) + 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(PipPackagesWidget.PackageColumn) - for itm in self.__allUpdateableItems()] + packages = [ + itm.text(PipPackagesWidget.PackageColumn) + 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(PipPackagesWidget.PackageColumn) - for itm in self.packagesList.selectedItems()] + packages = [ + itm.text(PipPackagesWidget.PackageColumn) + 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()) + 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()) + packages, + venvName=self.environmentsComboBox.currentText(), + userSite=self.userCheckBox.isChecked(), + ) if ok: self.on_refreshButton_clicked() - + @pyqtSlot() def on_showPackageDetailsButton_clicked(self): """ @@ -757,144 +747,135 @@ item = self.packagesList.selectedItems()[0] if item: packageName = item.text(PipPackagesWidget.PackageColumn) - upgradable = bool(item.text( - PipPackagesWidget.AvailableVersionColumn)) + upgradable = bool(item.text(PipPackagesWidget.AvailableVersionColumn)) # show details for available version or installed one if item.text(PipPackagesWidget.AvailableVersionColumn): - packageVersion = item.text( - PipPackagesWidget.AvailableVersionColumn) + packageVersion = item.text(PipPackagesWidget.AvailableVersionColumn) else: - packageVersion = item.text( - PipPackagesWidget.InstalledVersionColumn) - - self.__showPackageDetails(packageName, packageVersion, - upgradable=upgradable) - + packageVersion = item.text(PipPackagesWidget.InstalledVersionColumn) + + 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() + 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() + 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() + bool(self.searchEditName.text()) and self.__isPipAvailable() ) - + def __updateSearchMoreButton(self, enable): """ Private method to update the state of the search more button. - + @param enable flag indicating the desired enable state @type bool """ self.searchMoreButton.setEnabled( - enable and - bool(self.searchEditName.text()) and - self.__isPipAvailable() + enable and 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() self.__updateSearchMoreButton(False) - + @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() - ): + if bool(self.searchEditName.text()) and self.__isPipAvailable(): self.__searchFirst() - + @pyqtSlot() def on_searchButton_clicked(self): """ Private slot handling a press of the search button. """ self.__searchFirst() - + @pyqtSlot() def on_searchMoreButton_clicked(self): """ Private slot handling a press of the search more button. """ self.__search(self.__lastSearchPage + 1) - + @pyqtSlot() def on_searchResultList_itemSelectionChanged(self): """ Private slot handling changes of the search result selection. """ self.__updateSearchActionButtons() - + def __searchFirst(self): """ Private method to perform the search for packages. """ self.searchResultList.clear() self.searchInfoLabel.clear() - + self.__updateSearchMoreButton(False) - + self.__search() - + def __search(self, page=1): """ Private method to perform the search by calling the PyPI search URL. - + @param page search page to retrieve (defaults to 1) @type int (optional) """ self.__lastSearchPage = page - + self.searchButton.setEnabled(False) - + searchTerm = self.searchEditName.text().strip() searchTerm = bytes(QUrl.toPercentEncoding(searchTerm)).decode() urlQuery = QUrlQuery() @@ -902,29 +883,29 @@ urlQuery.addQueryItem("page", str(page)) url = QUrl(self.__pip.getIndexUrlSearch()) url.setQuery(urlQuery) - + request = QNetworkRequest(QUrl(url)) request.setAttribute( QNetworkRequest.Attribute.CacheLoadControlAttribute, - QNetworkRequest.CacheLoadControl.AlwaysNetwork) + QNetworkRequest.CacheLoadControl.AlwaysNetwork, + ) reply = self.__pip.getNetworkAccessManager().get(request) - reply.finished.connect( - lambda: self.__searchResponse(reply)) + 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: EricMessageBox.warning( None, @@ -932,78 +913,86 @@ self.tr( "<p>Received an error while searching for <b>{0}</b>.</p>" "<p>Error: {1}</p>" - ).format(searchTerm, reply.errorString()) + ).format(searchTerm, reply.errorString()), ) reply.deleteLater() return - + data = bytes(reply.readAll()).decode() reply.deleteLater() - + results = PypiSearchResultsParser(data).getResults() if results: # PyPI returns max. 20 entries per page if len(results) < 20: - msg = self.tr("%n package(s) found.", "", - (self.__lastSearchPage - 1) * 20 + len(results)) + msg = self.tr( + "%n package(s) found.", + "", + (self.__lastSearchPage - 1) * 20 + len(results), + ) self.__updateSearchMoreButton(False) else: msg = self.tr("Showing first {0} packages found.").format( - self.__lastSearchPage * 20) + self.__lastSearchPage * 20 + ) self.__updateSearchMoreButton(True) self.searchInfoLabel.setText(msg) lastItem = self.searchResultList.topLevelItem( - self.searchResultList.topLevelItemCount() - 1) + self.searchResultList.topLevelItemCount() - 1 + ) else: self.__updateSearchMoreButton(False) if self.__lastSearchPage == 1: EricMessageBox.warning( self, self.tr("Search PyPI"), - self.tr("""<p>There were no results for <b>{0}</b>.</p>""") - .format(searchTerm) + self.tr("""<p>There were no results for <b>{0}</b>.</p>""").format( + searchTerm + ), ) self.searchInfoLabel.setText( - self.tr("""<p>There were no results for <b>{0}</b>.</p>""") - .format(searchTerm) + self.tr("""<p>There were no results for <b>{0}</b>.</p>""").format( + searchTerm + ) ) else: EricMessageBox.warning( self, self.tr("Search PyPI"), - self.tr("""<p>There were no more results for""" - """ <b>{0}</b>.</p>""").format(searchTerm) + self.tr( + """<p>There were no more results for""" """ <b>{0}</b>.</p>""" + ).format(searchTerm), ) lastItem = None - + wrapper = textwrap.TextWrapper(width=80) for result in results: try: - description = "\n".join([ - wrapper.fill(line) for line in - result['description'].strip().splitlines() - ]) + description = "\n".join( + [ + wrapper.fill(line) + for line in result["description"].strip().splitlines() + ] + ) except KeyError: description = "" - date = ( - result["released"] - if "released" in result else - result["created"] - ) + date = result["released"] if "released" in result else result["created"] itm = QTreeWidgetItem( - self.searchResultList, [ - result['name'].strip(), - result['version'], + self.searchResultList, + [ + result["name"].strip(), + result["version"], date.strip(), description, - ]) - itm.setData(0, self.SearchVersionRole, result['version']) - + ], + ) + itm.setData(0, self.SearchVersionRole, result["version"]) + if lastItem: self.searchResultList.scrollToItem( - lastItem, - QAbstractItemView.ScrollHint.PositionAtTop) - + lastItem, QAbstractItemView.ScrollHint.PositionAtTop + ) + header = self.searchResultList.header() header.setStretchLastSection(False) header.resizeSections(QHeaderView.ResizeMode.ResizeToContents) @@ -1012,44 +1001,42 @@ 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() + 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() + 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 @@ -1057,52 +1044,51 @@ """ venvName = self.environmentsComboBox.currentText() if venvName and packages: - self.__pip.installPackages(packages, venvName=venvName, - userSite=userSite) + 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): + + 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 @@ -1115,43 +1101,44 @@ @type bool (optional) """ with EricOverrideCursor(): - packageData = self.__pip.getPackageDetails( - packageName, packageVersion) - + 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 + 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 = PipPackageDetailsDialog( + packageData, buttonsMode=buttonsMode, parent=self ) self.__packageDetailsDialog.show() else: EricMessageBox.warning( self, self.tr("Search PyPI"), - self.tr("""<p>No package details info for <b>{0}</b>""" - """ available.</p>""").format(packageName)) - + 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 @@ -1159,81 +1146,80 @@ """ self.__pipMenu = QMenu() self.__installPipAct = self.__pipMenu.addAction( - self.tr("Install Pip"), - self.__installPip) + self.tr("Install Pip"), self.__installPip + ) self.__installPipUserAct = self.__pipMenu.addAction( - self.tr("Install Pip to User-Site"), - self.__installPipUser) + self.tr("Install Pip to User-Site"), self.__installPipUser + ) self.__repairPipAct = self.__pipMenu.addAction( - self.tr("Repair Pip"), - self.__repairPip) + self.tr("Repair Pip"), self.__repairPip + ) self.__pipMenu.addSeparator() self.__installPackagesAct = self.__pipMenu.addAction( - self.tr("Install Packages"), - self.__installPackages) + self.tr("Install Packages"), self.__installPackages + ) self.__installLocalPackageAct = self.__pipMenu.addAction( - self.tr("Install Local Package"), - self.__installLocalPackage) + self.tr("Install Local Package"), self.__installLocalPackage + ) self.__pipMenu.addSeparator() self.__installRequirementsAct = self.__pipMenu.addAction( - self.tr("Install Requirements"), - self.__installRequirements) + self.tr("Install Requirements"), self.__installRequirements + ) self.__reinstallPackagesAct = self.__pipMenu.addAction( - self.tr("Re-Install Selected Packages"), - self.__reinstallPackages) + self.tr("Re-Install Selected Packages"), self.__reinstallPackages + ) self.__uninstallRequirementsAct = self.__pipMenu.addAction( - self.tr("Uninstall Requirements"), - self.__uninstallRequirements) + self.tr("Uninstall Requirements"), self.__uninstallRequirements + ) self.__generateRequirementsAct = self.__pipMenu.addAction( - self.tr("Generate Requirements..."), - self.__generateRequirements) + self.tr("Generate Requirements..."), self.__generateRequirements + ) self.__pipMenu.addSeparator() self.__showLicensesDialogAct = self.__pipMenu.addAction( - self.tr("Show Licenses..."), - self.__showLicensesDialog) + self.tr("Show Licenses..."), self.__showLicensesDialog + ) self.__pipMenu.addSeparator() self.__checkVulnerabilityAct = self.__pipMenu.addAction( - self.tr("Check Vulnerabilities"), - self.__updateVulnerabilityData) + self.tr("Check Vulnerabilities"), self.__updateVulnerabilityData + ) # updateVulnerabilityDbAct self.__pipMenu.addAction( - self.tr("Update Vulnerability Database"), - self.__updateVulnerabilityDbCache) + self.tr("Update Vulnerability Database"), self.__updateVulnerabilityDbCache + ) self.__pipMenu.addSeparator() self.__cyclonedxAct = self.__pipMenu.addAction( - self.tr("Create SBOM file"), - self.__createSBOMFile) + self.tr("Create SBOM file"), self.__createSBOMFile + ) self.__pipMenu.addSeparator() self.__cacheInfoAct = self.__pipMenu.addAction( - self.tr("Show Cache Info..."), - self.__showCacheInfo) + self.tr("Show Cache Info..."), self.__showCacheInfo + ) self.__cacheShowListAct = self.__pipMenu.addAction( - self.tr("Show Cached Files..."), - self.__showCacheList) + self.tr("Show Cached Files..."), self.__showCacheList + ) self.__cacheRemoveAct = self.__pipMenu.addAction( - self.tr("Remove Cached Files..."), - self.__removeCachedFiles) + self.tr("Remove Cached Files..."), self.__removeCachedFiles + ) self.__cachePurgeAct = self.__pipMenu.addAction( - self.tr("Purge Cache..."), - self.__purgeCache) + self.tr("Purge Cache..."), self.__purgeCache + ) self.__pipMenu.addSeparator() # editUserConfigAct self.__pipMenu.addAction( - self.tr("Edit User Configuration..."), - self.__editUserConfiguration) + self.tr("Edit User Configuration..."), self.__editUserConfiguration + ) self.__editVirtualenvConfigAct = self.__pipMenu.addAction( self.tr("Edit Environment Configuration..."), - self.__editVirtualenvConfiguration) + self.__editVirtualenvConfiguration, + ) self.__pipMenu.addSeparator() # pipConfigAct - self.__pipMenu.addAction( - self.tr("Configure..."), - self.__pipConfigure) + 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. @@ -1241,33 +1227,34 @@ 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) - + self.__checkVulnerabilityAct.setEnabled( - enable & self.vulnerabilityCheckBox.isEnabled()) - + enable & self.vulnerabilityCheckBox.isEnabled() + ) + self.__cyclonedxAct.setEnabled(enable) - + self.__showLicensesDialogAct.setEnabled(enable) - + @pyqtSlot() def __installPip(self): """ @@ -1277,7 +1264,7 @@ if venvName: self.__pip.installPip(venvName) self.on_refreshButton_clicked() - + @pyqtSlot() def __installPipUser(self): """ @@ -1288,7 +1275,7 @@ if venvName: self.__pip.installPip(venvName, userSite=True) self.on_refreshButton_clicked() - + @pyqtSlot() def __repairPip(self): """ @@ -1299,7 +1286,7 @@ if venvName: self.__pip.repairPip(venvName) self.on_refreshButton_clicked() - + @pyqtSlot() def __installPackages(self): """ @@ -1308,11 +1295,12 @@ 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): """ @@ -1321,25 +1309,27 @@ 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(PipPackagesWidget.PackageColumn) - for itm in self.packagesList.selectedItems()] + packages = [ + itm.text(PipPackagesWidget.PackageColumn) + for itm in self.packagesList.selectedItems() + ] venvName = self.environmentsComboBox.currentText() if venvName and packages: - self.__pip.installPackages(packages, venvName=venvName, - forceReinstall=True) + self.__pip.installPackages(packages, venvName=venvName, forceReinstall=True) self.on_refreshButton_clicked() - + @pyqtSlot() def __installRequirements(self): """ @@ -1349,7 +1339,7 @@ if venvName: self.__pip.installRequirements(venvName) self.on_refreshButton_clicked() - + @pyqtSlot() def __uninstallRequirements(self): """ @@ -1359,7 +1349,7 @@ if venvName: self.__pip.uninstallRequirements(venvName) self.on_refreshButton_clicked() - + @pyqtSlot() def __generateRequirements(self): """ @@ -1368,17 +1358,18 @@ 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): """ @@ -1387,15 +1378,16 @@ 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: @@ -1407,10 +1399,10 @@ EricMessageBox.critical( None, self.tr("Edit Configuration"), - self.tr("""No valid configuration path determined.""" - """ Aborting""")) + self.tr("""No valid configuration path determined.""" """ Aborting"""), + ) return - + try: if not os.path.isdir(cfgDir): os.makedirs(cfgDir) @@ -1418,32 +1410,32 @@ EricMessageBox.critical( None, self.tr("Edit Configuration"), - self.tr("""No valid configuration path determined.""" - """ Aborting""")) + 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): EricMessageBox.critical( None, self.tr("Edit Configuration"), - self.tr("""No valid configuration path determined.""" - """ Aborting""")) + 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. """ ericApp().getObject("UserInterface").showPreferences("pipPage") - + @pyqtSlot() def __showCacheInfo(self): """ @@ -1452,7 +1444,7 @@ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.showCacheInfo(venvName) - + @pyqtSlot() def __showCacheList(self): """ @@ -1461,7 +1453,7 @@ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.cacheList(venvName) - + @pyqtSlot() def __removeCachedFiles(self): """ @@ -1470,7 +1462,7 @@ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.cacheRemove(venvName) - + @pyqtSlot() def __purgeCache(self): """ @@ -1479,25 +1471,26 @@ venvName = self.environmentsComboBox.currentText() if venvName: self.__pip.cachePurge(venvName) - + ################################################################## ## Interface to the vulnerability checks below ################################################################## - + @pyqtSlot(bool) def on_vulnerabilityCheckBox_clicked(self, checked): """ Private slot handling a change of the automatic vulnerability checks. - + @param checked flag indicating the state of the check box @type bool """ if checked: self.__updateVulnerabilityData(clearFirst=True) - + self.packagesList.header().setSectionHidden( - PipPackagesWidget.VulnerabilityColumn, not checked) - + PipPackagesWidget.VulnerabilityColumn, not checked + ) + @pyqtSlot() def __clearVulnerabilityInfo(self): """ @@ -1508,64 +1501,66 @@ itm.setText(PipPackagesWidget.VulnerabilityColumn, "") itm.setToolTip(PipPackagesWidget.VulnerabilityColumn, "") itm.setIcon(PipPackagesWidget.VulnerabilityColumn, QIcon()) - itm.setData(PipPackagesWidget.VulnerabilityColumn, - PipPackagesWidget.VulnerabilityRole, - None) - + itm.setData( + PipPackagesWidget.VulnerabilityColumn, + PipPackagesWidget.VulnerabilityRole, + None, + ) + @pyqtSlot() def __updateVulnerabilityData(self, clearFirst=True): """ Private slot to update the shown vulnerability info. - + @param clearFirst flag indicating to clear the vulnerability info first (defaults to True) @type bool (optional) """ if clearFirst: self.__clearVulnerabilityInfo() - + packages = [] for row in range(self.packagesList.topLevelItemCount()): itm = self.packagesList.topLevelItem(row) - packages.append(Package( - name=itm.text(PipPackagesWidget.PackageColumn), - version=itm.text(PipPackagesWidget.InstalledVersionColumn) - )) - - error, vulnerabilities = ( - self.__pip.getVulnerabilityChecker().check(packages) - ) + packages.append( + Package( + name=itm.text(PipPackagesWidget.PackageColumn), + version=itm.text(PipPackagesWidget.InstalledVersionColumn), + ) + ) + + error, vulnerabilities = self.__pip.getVulnerabilityChecker().check(packages) if error == VulnerabilityCheckError.OK: for package in vulnerabilities: items = self.packagesList.findItems( - package, - Qt.MatchFlag.MatchExactly | - Qt.MatchFlag.MatchCaseSensitive + package, Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive ) if items: itm = items[0] itm.setData( PipPackagesWidget.VulnerabilityColumn, PipPackagesWidget.VulnerabilityRole, - vulnerabilities[package] + vulnerabilities[package], ) affected = {v.spec for v in vulnerabilities[package]} itm.setText( - PipPackagesWidget.VulnerabilityColumn, - ', '.join(affected) + PipPackagesWidget.VulnerabilityColumn, ", ".join(affected) ) itm.setIcon( PipPackagesWidget.VulnerabilityColumn, - UI.PixmapCache.getIcon("securityLow") + UI.PixmapCache.getIcon("securityLow"), ) - - elif error in (VulnerabilityCheckError.FullDbUnavailable, - VulnerabilityCheckError.SummaryDbUnavailable): + + elif error in ( + VulnerabilityCheckError.FullDbUnavailable, + VulnerabilityCheckError.SummaryDbUnavailable, + ): self.vulnerabilityCheckBox.setChecked(False) self.vulnerabilityCheckBox.setEnabled(False) self.packagesList.setColumnHidden( - PipPackagesWidget.VulnerabilityColumn, True) - + PipPackagesWidget.VulnerabilityColumn, True + ) + @pyqtSlot() def __updateVulnerabilityDbCache(self): """ @@ -1574,12 +1569,13 @@ """ with EricOverrideCursor(): self.__pip.getVulnerabilityChecker().updateVulnerabilityDb() - - def __showVulnerabilityInformation(self, packageName, packageVersion, - vulnerabilities): + + def __showVulnerabilityInformation( + self, packageName, packageVersion, vulnerabilities + ): """ Private method to show the detected vulnerability data. - + @param packageName name of the package @type str @param packageVersion installed version number @@ -1587,9 +1583,8 @@ @param vulnerabilities list of vulnerabilities @type list of Vulnerability """ - header = ( - self.tr("{0} {1}", "package name, package version") - .format(packageName, packageVersion) + header = self.tr("{0} {1}", "package name, package version").format( + packageName, packageVersion ) topItem = QTreeWidgetItem(self.infoWidget, [header]) topItem.setFirstColumnSpanned(True) @@ -1597,78 +1592,76 @@ font = topItem.font(0) font.setBold(True) topItem.setFont(0, font) - + for vulnerability in vulnerabilities: title = ( vulnerability.cve - if vulnerability.cve else - vulnerability.vulnerabilityId + if vulnerability.cve + else vulnerability.vulnerabilityId ) titleItem = QTreeWidgetItem(topItem, [title]) titleItem.setFirstColumnSpanned(True) titleItem.setExpanded(True) - + QTreeWidgetItem( - titleItem, - [self.tr("Affected Version:"), vulnerability.spec]) + titleItem, [self.tr("Affected Version:"), vulnerability.spec] + ) itm = QTreeWidgetItem( - titleItem, - [self.tr("Advisory:"), vulnerability.advisory]) - itm.setToolTip(1, "<p>{0}</p>".format( - vulnerability.advisory.replace("\r\n", "<br/>") - )) - + titleItem, [self.tr("Advisory:"), vulnerability.advisory] + ) + itm.setToolTip( + 1, "<p>{0}</p>".format(vulnerability.advisory.replace("\r\n", "<br/>")) + ) + self.infoWidget.scrollToTop() self.infoWidget.resizeColumnToContents(0) - + header = self.infoWidget.header() header.setStretchLastSection(True) - + ####################################################################### ## Dependency tree related methods below ####################################################################### - + @pyqtSlot(bool) def on_viewToggleButton_toggled(self, checked): """ Private slot handling the view selection. - + @param checked state of the toggle button @type bool """ if checked: - self.viewsStackWidget.setCurrentWidget( - self.dependenciesPage) + self.viewsStackWidget.setCurrentWidget(self.dependenciesPage) self.__refreshDependencyTree() else: - self.viewsStackWidget.setCurrentWidget( - self.packagesPage) + self.viewsStackWidget.setCurrentWidget(self.packagesPage) self.__refreshPackagesList() - + @pyqtSlot(bool) def on_requiresButton_toggled(self, checked): """ Private slot handling the selection of the view type. - + @param checked state of the radio button (unused) @type bool """ self.__refreshDependencyTree() - + @pyqtSlot() def on_localDepCheckBox_clicked(self): """ Private slot handling the switching of the local mode. """ self.__refreshDependencyTree() - + @pyqtSlot() def on_userDepCheckBox_clicked(self): """ Private slot handling the switching of the 'user-site' mode. """ self.__refreshDependencyTree() - + def __refreshDependencyTree(self): """ Private method to refresh the dependency tree. @@ -1685,80 +1678,81 @@ usersite=self.userDepCheckBox.isChecked(), reverse=self.requiredByButton.isChecked(), ) - + self.dependenciesList.setUpdatesEnabled(False) for dependency in dependencies: self.__addDependency(dependency, self.dependenciesList) - + self.dependenciesList.sortItems( - PipPackagesWidget.DepPackageColumn, - Qt.SortOrder.AscendingOrder) + PipPackagesWidget.DepPackageColumn, Qt.SortOrder.AscendingOrder + ) for col in range(self.dependenciesList.columnCount()): self.dependenciesList.resizeColumnToContents(col) self.dependenciesList.setUpdatesEnabled(True) - + self.__updateDepActionButtons() - + def __addDependency(self, dependency, parent): """ Private method to add a dependency branch to a given parent. - + @param dependency dependency to be added @type dict @param parent reference to the parent item @type QTreeWidget or QTreeWidgetItem """ - itm = QTreeWidgetItem(parent, [ - dependency["package_name"], - dependency["installed_version"], - dependency["required_version"], - ]) + itm = QTreeWidgetItem( + parent, + [ + dependency["package_name"], + dependency["installed_version"], + dependency["required_version"], + ], + ) itm.setExpanded(True) - + if dependency["installed_version"] == "?": - itm.setText(PipPackagesWidget.DepInstalledVersionColumn, - self.tr("unknown")) - + itm.setText(PipPackagesWidget.DepInstalledVersionColumn, self.tr("unknown")) + if dependency["required_version"].lower() not in ("any", "?"): spec = ( "=={0}".format(dependency["required_version"]) - if dependency["required_version"][0] in "0123456789" else - dependency["required_version"] + if dependency["required_version"][0] in "0123456789" + else dependency["required_version"] ) specifierSet = SpecifierSet(specifiers=spec) if not specifierSet.contains(dependency["installed_version"]): - itm.setIcon(PipPackagesWidget.DepRequiredVersionColumn, - UI.PixmapCache.getIcon("warning")) - + itm.setIcon( + PipPackagesWidget.DepRequiredVersionColumn, + UI.PixmapCache.getIcon("warning"), + ) + elif dependency["required_version"].lower() == "any": - itm.setText(PipPackagesWidget.DepRequiredVersionColumn, - self.tr("any")) - + itm.setText(PipPackagesWidget.DepRequiredVersionColumn, self.tr("any")) + elif dependency["required_version"] == "?": - itm.setText(PipPackagesWidget.DepRequiredVersionColumn, - self.tr("unknown")) - + itm.setText(PipPackagesWidget.DepRequiredVersionColumn, self.tr("unknown")) + # recursively add sub-dependencies for dep in dependency["dependencies"]: self.__addDependency(dep, itm) - + @pyqtSlot(QTreeWidgetItem, int) def on_dependenciesList_itemActivated(self, item, column): """ Private slot reacting on a package item of the dependency tree being activated. - + @param item reference to the activated item @type QTreeWidgetItem @param column activated column @type int """ packageName = item.text(PipPackagesWidget.DepPackageColumn) - packageVersion = item.text( - PipPackagesWidget.DepInstalledVersionColumn) - + packageVersion = item.text(PipPackagesWidget.DepInstalledVersionColumn) + self.__showPackageDetails(packageName, packageVersion) - + @pyqtSlot() def on_dependenciesList_itemSelectionChanged(self): """ @@ -1767,28 +1761,27 @@ """ if len(self.dependenciesList.selectedItems()) == 0: self.dependencyInfoWidget.clear() - + @pyqtSlot(QTreeWidgetItem, int) def on_dependenciesList_itemPressed(self, item, column): """ Private slot reacting on a package item of the dependency tree being pressed. - + @param item reference to the pressed item @type QTreeWidgetItem @param column pressed column @type int """ self.dependencyInfoWidget.clear() - + if item is not None: self.__showPackageInformation( - item.text(PipPackagesWidget.DepPackageColumn), - self.dependencyInfoWidget + item.text(PipPackagesWidget.DepPackageColumn), self.dependencyInfoWidget ) - + self.__updateDepActionButtons() - + @pyqtSlot() def on_refreshDependenciesButton_clicked(self): """ @@ -1797,19 +1790,19 @@ currentEnvironment = self.environmentsComboBox.currentText() self.environmentsComboBox.clear() self.dependenciesList.clear() - + with EricOverrideCursor(): self.__populateEnvironments() - + index = self.environmentsComboBox.findText( currentEnvironment, - Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive + Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive, ) if index != -1: self.environmentsComboBox.setCurrentIndex(index) - + self.__updateDepActionButtons() - + @pyqtSlot() def on_showDepPackageDetailsButton_clicked(self): """ @@ -1819,24 +1812,22 @@ item = self.dependenciesList.selectedItems()[0] if item: packageName = item.text(PipPackagesWidget.DepPackageColumn) - packageVersion = item.text( - PipPackagesWidget.DepInstalledVersionColumn) - + packageVersion = item.text(PipPackagesWidget.DepInstalledVersionColumn) + self.__showPackageDetails(packageName, packageVersion) - + def __updateDepActionButtons(self): """ Private method to set the state of the dependency page action buttons. """ self.showDepPackageDetailsButton.setEnabled( - len(self.dependenciesList.selectedItems()) == 1 and - self.__isPipAvailable() + len(self.dependenciesList.selectedItems()) == 1 and self.__isPipAvailable() ) - + ################################################################## ## Interface to show the licenses dialog below ################################################################## - + @pyqtSlot() def __showLicensesDialog(self): """ @@ -1844,38 +1835,38 @@ environment. """ from .PipLicensesDialog import PipLicensesDialog - + environment = self.environmentsComboBox.currentText() localPackages = ( self.localDepCheckBox.isChecked() - if self.viewToggleButton.isChecked() else - self.localCheckBox.isChecked() + if self.viewToggleButton.isChecked() + else self.localCheckBox.isChecked() ) usersite = ( self.userDepCheckBox.isChecked() - if self.viewToggleButton.isChecked() else - self.userCheckBox.isChecked() + if self.viewToggleButton.isChecked() + else self.userCheckBox.isChecked() ) dlg = PipLicensesDialog( self.__pip, environment, localPackages=localPackages, usersite=usersite, - parent=self + parent=self, ) dlg.exec() - + ################################################################## ## Interface to create a SBOM file using CycloneDX ################################################################## - + @pyqtSlot() def __createSBOMFile(self): """ Private slot to create a "Software Bill Of Material" file. """ import CycloneDXInterface - + venvName = self.environmentsComboBox.currentText() if venvName == self.__pip.getProjectEnvironmentString(): venvName = "<project>"