Sat, 22 May 2021 18:51:46 +0200
Renamed the modules and classes of the E5Gui package to have the prefix 'Eric' instead of 'E5'.
# -*- coding: utf-8 -*- # Copyright (c) 2019 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing the conda packages management widget. """ import os from PyQt6.QtCore import pyqtSlot, Qt from PyQt6.QtWidgets import ( QWidget, QToolButton, QMenu, QTreeWidgetItem, QApplication, QLineEdit, QDialog ) from E5Gui import EricFileDialog, EricMessageBox, EricTextInputDialog from E5Gui.EricApplication import ericApp from E5Gui.EricOverrideCursor import EricOverrideCursor from .Ui_CondaPackagesWidget import Ui_CondaPackagesWidget import UI.PixmapCache import CondaInterface class CondaPackagesWidget(QWidget, Ui_CondaPackagesWidget): """ Class implementing the conda packages management widget. """ # Role definition of packages list PackageVersionRole = Qt.ItemDataRole.UserRole + 1 PackageBuildRole = Qt.ItemDataRole.UserRole + 2 # Role definitions of search results list PackageDetailedDataRole = Qt.ItemDataRole.UserRole + 1 def __init__(self, conda, parent=None): """ Constructor @param conda reference to the conda interface @type Conda @param parent reference to the parent widget @type QWidget """ super().__init__(parent) self.setupUi(self) self.__conda = conda if not CondaInterface.isCondaAvailable(): self.baseWidget.hide() self.searchWidget.hide() else: self.notAvailableWidget.hide() self.__initCondaInterface() def __initCondaInterface(self): """ Private method to initialize the conda interface elements. """ self.statusLabel.hide() self.condaMenuButton.setObjectName( "conda_supermenu_button") self.condaMenuButton.setIcon(UI.PixmapCache.getIcon("superMenu")) self.condaMenuButton.setToolTip(self.tr("Conda Menu")) self.condaMenuButton.setPopupMode( QToolButton.ToolButtonPopupMode.InstantPopup) self.condaMenuButton.setToolButtonStyle( Qt.ToolButtonStyle.ToolButtonIconOnly) self.condaMenuButton.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.condaMenuButton.setAutoRaise(True) self.condaMenuButton.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.searchToggleButton.setIcon(UI.PixmapCache.getIcon("find")) self.searchButton.setIcon(UI.PixmapCache.getIcon("findNext")) self.installButton.setIcon(UI.PixmapCache.getIcon("plus")) self.showDetailsButton.setIcon(UI.PixmapCache.getIcon("info")) if CondaInterface.condaVersion() >= (4, 4, 0): self.searchOptionsWidget.hide() else: self.platformComboBox.addItems(sorted([ "", "win-32", "win-64", "osx-64", "linux-32", "linux-64", ])) self.__initCondaMenu() self.__populateEnvironments() self.__updateActionButtons() self.searchWidget.hide() self.__conda.condaEnvironmentCreated.connect( self.on_refreshButton_clicked) self.__conda.condaEnvironmentRemoved.connect( self.on_refreshButton_clicked) def __populateEnvironments(self): """ Private method to get a list of environments and populate the selector. """ environments = [("", "")] + sorted( self.__conda.getCondaEnvironmentsList()) for environment in environments: self.environmentsComboBox.addItem(environment[0], environment[1]) def __initCondaMenu(self): """ Private method to create the super menu and attach it to the super menu button. """ self.__condaMenu = QMenu(self) self.__envActs = [] self.__cleanMenu = QMenu(self.tr("Clean"), self) self.__cleanMenu.addAction( self.tr("All"), lambda: self.__conda.cleanConda("all")) self.__cleanMenu.addAction( self.tr("Cache"), lambda: self.__conda.cleanConda("index-cache")) self.__cleanMenu.addAction( self.tr("Lock Files"), lambda: self.__conda.cleanConda("lock")) self.__cleanMenu.addAction( self.tr("Packages"), lambda: self.__conda.cleanConda("packages")) self.__cleanMenu.addAction( self.tr("Tarballs"), lambda: self.__conda.cleanConda("tarballs")) self.__condaMenu.addAction( self.tr("About Conda..."), self.__aboutConda) self.__condaMenu.addSeparator() self.__condaMenu.addAction( self.tr("Update Conda"), self.__conda.updateConda) self.__condaMenu.addSeparator() self.__envActs.append(self.__condaMenu.addAction( self.tr("Install Packages"), self.__installPackages)) self.__envActs.append(self.__condaMenu.addAction( self.tr("Install Requirements"), self.__installRequirements)) self.__condaMenu.addSeparator() self.__envActs.append(self.__condaMenu.addAction( self.tr("Generate Requirements"), self.__generateRequirements)) self.__condaMenu.addSeparator() self.__condaMenu.addAction( self.tr("Create Environment from Requirements"), self.__createEnvironment) self.__envActs.append(self.__condaMenu.addAction( self.tr("Clone Environment"), self.__cloneEnvironment)) self.__deleteEnvAct = self.__condaMenu.addAction( self.tr("Delete Environment"), self.__deleteEnvironment) self.__condaMenu.addSeparator() self.__condaMenu.addMenu(self.__cleanMenu) self.__condaMenu.addSeparator() self.__condaMenu.addAction( self.tr("Edit User Configuration..."), self.__editUserConfiguration) self.__condaMenu.addSeparator() self.__condaMenu.addAction( self.tr("Configure..."), self.__condaConfigure) self.condaMenuButton.setMenu(self.__condaMenu) self.__condaMenu.aboutToShow.connect(self.__aboutToShowCondaMenu) 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. """ 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): """ Private slot handling the selection of a conda environment. @param index index of the selected conda environment @type int """ self.packagesList.clear() prefix = self.environmentsComboBox.itemData(index) if prefix: self.statusLabel.show() self.statusLabel.setText(self.tr("Getting installed packages...")) with EricOverrideCursor(): # 1. populate with installed packages self.packagesList.setUpdatesEnabled(False) installedPackages = self.__conda.getInstalledPackages( prefix=prefix) for package, version, build in installedPackages: itm = QTreeWidgetItem(self.packagesList, [package, version]) itm.setData(1, self.PackageVersionRole, version) itm.setData(1, self.PackageBuildRole, build) self.packagesList.setUpdatesEnabled(True) self.statusLabel.setText( self.tr("Getting outdated packages...")) QApplication.processEvents() # 2. update with update information self.packagesList.setUpdatesEnabled(False) updateablePackages = self.__conda.getUpdateablePackages( prefix=prefix) for package, version, build in updateablePackages: items = self.packagesList.findItems( package, Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive) if items: itm = items[0] itm.setText(2, version) itm.setData(2, self.PackageVersionRole, version) itm.setData(2, self.PackageBuildRole, build) if itm.data(1, self.PackageVersionRole) == version: # build must be different, show in version display itm.setText(1, self.tr("{0} (Build: {1})").format( itm.data(1, self.PackageVersionRole), itm.data(1, self.PackageBuildRole), )) itm.setText(2, self.tr("{0} (Build: {1})").format( itm.data(2, self.PackageVersionRole), itm.data(2, self.PackageBuildRole), )) 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() @pyqtSlot() def on_packagesList_itemSelectionChanged(self): """ Private slot to handle the selection of some items.. """ self.__updateActionButtons() @pyqtSlot() def on_refreshButton_clicked(self): """ Private slot to refresh the display. """ 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) 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: prefix = self.environmentsComboBox.itemData( self.environmentsComboBox.currentIndex()) ok = self.__conda.updatePackages(packages, prefix=prefix) if ok: self.on_refreshButton_clicked() @pyqtSlot() def on_upgradeAllButton_clicked(self): """ Private slot to upgrade all packages of the selected environment. """ prefix = self.environmentsComboBox.itemData( self.environmentsComboBox.currentIndex()) ok = self.__conda.updateAllPackages(prefix=prefix) 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: prefix = self.environmentsComboBox.itemData( self.environmentsComboBox.currentIndex()) ok = self.__conda.uninstallPackages(packages, prefix=prefix) if ok: self.on_refreshButton_clicked() ####################################################################### ## Search widget related methods below ####################################################################### def __updateSearchActionButtons(self): """ Private method to update the action button states of the search widget. """ enable = len(self.searchResultList.selectedItems()) == 1 self.installButton.setEnabled( enable and self.environmentsComboBox.currentIndex() > 0) self.showDetailsButton.setEnabled( enable and bool(self.searchResultList.selectedItems()[0].parent())) def __doSearch(self): """ Private method to search for packages. """ self.searchResultList.clear() pattern = self.searchEdit.text() if pattern: with EricOverrideCursor(): prefix = ( "" if CondaInterface.condaVersion() >= (4, 4, 0) else self.environmentsComboBox.itemData( self.environmentsComboBox.currentIndex()) ) ok, result = self.__conda.searchPackages( pattern, fullNameOnly=self.fullNameButton.isChecked(), packageSpec=self.packageSpecButton.isChecked(), platform=self.platformComboBox.currentText(), prefix=prefix, ) if ok and result: self.searchResultList.setUpdatesEnabled(False) for package in result: itm = QTreeWidgetItem(self.searchResultList, [package]) itm.setExpanded(False) for detail in result[package]: version = detail["version"] build = detail["build"] if "subdir" in detail: platform = detail["subdir"] elif "platform" in detail: platform = detail["platform"] else: platform = "" citm = QTreeWidgetItem( itm, ["", version, build, platform]) citm.setData(0, self.PackageDetailedDataRole, detail) self.searchResultList.sortItems( 0, Qt.SortOrder.AscendingOrder) self.searchResultList.resizeColumnToContents(0) self.searchResultList.setUpdatesEnabled(True) if not ok: try: message = result["message"] except KeyError: message = result["error"] EricMessageBox.warning( self, self.tr("Conda Search Package Error"), message) def __showDetails(self, item): """ Private method to show a dialog with details about a package item. @param item reference to the package item @type QTreeWidgetItem """ details = item.data(0, self.PackageDetailedDataRole) if details: from .CondaPackageDetailsWidget import CondaPackageDetailsDialog dlg = CondaPackageDetailsDialog(details, self) dlg.exec() @pyqtSlot(str) def on_searchEdit_textChanged(self, txt): """ Private slot handling changes of the entered search specification. @param txt current search entry @type str """ self.searchButton.setEnabled(bool(txt)) @pyqtSlot() def on_searchEdit_returnPressed(self): """ Private slot handling the user pressing the Return button in the search edit. """ self.__doSearch() @pyqtSlot() def on_searchButton_clicked(self): """ Private slot handling the press of the search button. """ self.__doSearch() @pyqtSlot() def on_installButton_clicked(self): """ Private slot to install a selected package. """ if len(self.searchResultList.selectedItems()) == 1: item = self.searchResultList.selectedItems()[0] if item.parent() is None: # it is just the package item package = item.text(0) else: # item with version and build package = "{0}={1}={2}".format( item.parent().text(0), item.text(1), item.text(2), ) prefix = self.environmentsComboBox.itemData( self.environmentsComboBox.currentIndex()) ok = self.__conda.installPackages([package], prefix=prefix) if ok: self.on_refreshButton_clicked() @pyqtSlot() def on_showDetailsButton_clicked(self): """ Private slot handling the 'Show Details' button. """ item = self.searchResultList.selectedItems()[0] self.__showDetails(item) @pyqtSlot() def on_searchResultList_itemSelectionChanged(self): """ Private slot handling a change of selected search results. """ self.__updateSearchActionButtons() @pyqtSlot(QTreeWidgetItem) def on_searchResultList_itemExpanded(self, item): """ Private slot handling the expansion of an item. @param item reference to the expanded item @type QTreeWidgetItem """ for col in range(1, self.searchResultList.columnCount()): self.searchResultList.resizeColumnToContents(col) @pyqtSlot(QTreeWidgetItem, int) def on_searchResultList_itemDoubleClicked(self, item, column): """ Private slot handling a double click of an item. @param item reference to the item that was double clicked @type QTreeWidgetItem @param column column of the double click @type int """ if item.parent() is not None: self.__showDetails(item) @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.searchEdit.setFocus(Qt.FocusReason.OtherFocusReason) self.searchEdit.selectAll() self.__updateSearchActionButtons() ####################################################################### ## Menu related methods below ####################################################################### @pyqtSlot() def __aboutToShowCondaMenu(self): """ Private slot to handle the conda menu about to be shown. """ selectedEnvironment = self.environmentsComboBox.currentText() enable = selectedEnvironment not in [""] for act in self.__envActs: act.setEnabled(enable) self.__deleteEnvAct.setEnabled( selectedEnvironment not in ["", self.__conda.RootName]) @pyqtSlot() def __aboutConda(self): """ Private slot to show some information about the conda installation. """ infoDict = self.__conda.getCondaInformation() from .CondaInfoDialog import CondaInfoDialog dlg = CondaInfoDialog(infoDict, self) dlg.exec() @pyqtSlot() def __installPackages(self): """ Private slot to install packages. """ prefix = self.environmentsComboBox.itemData( self.environmentsComboBox.currentIndex()) if prefix: ok, packageSpecs = EricTextInputDialog.getText( self, self.tr("Install Packages"), self.tr("Package Specifications (separated by whitespace):"), QLineEdit.EchoMode.Normal, minimumWidth=600) if ok and packageSpecs.strip(): packages = [p.strip() for p in packageSpecs.split()] ok = self.__conda.installPackages(packages, prefix=prefix) if ok: self.on_refreshButton_clicked() @pyqtSlot() def __installRequirements(self): """ Private slot to install packages from requirements files. """ prefix = self.environmentsComboBox.itemData( self.environmentsComboBox.currentIndex()) if prefix: requirements = EricFileDialog.getOpenFileNames( self, self.tr("Install Packages"), "", self.tr("Text Files (*.txt);;All Files (*)")) if requirements: args = [] for requirement in requirements: args.extend(["--file", requirement]) ok = self.__conda.installPackages(args, prefix=prefix) if ok: self.on_refreshButton_clicked() @pyqtSlot() def __generateRequirements(self): """ Private slot to generate a requirements file. """ prefix = self.environmentsComboBox.itemData( self.environmentsComboBox.currentIndex()) if prefix: env = self.environmentsComboBox.currentText() from .CondaExportDialog import CondaExportDialog self.__requirementsDialog = CondaExportDialog( self.__conda, env, prefix) self.__requirementsDialog.show() QApplication.processEvents() self.__requirementsDialog.start() @pyqtSlot() def __cloneEnvironment(self): """ Private slot to clone a conda environment. """ from .CondaNewEnvironmentDataDialog import ( CondaNewEnvironmentDataDialog) prefix = self.environmentsComboBox.itemData( self.environmentsComboBox.currentIndex()) if prefix: dlg = CondaNewEnvironmentDataDialog(self.tr("Clone Environment"), False, self) if dlg.exec() == QDialog.DialogCode.Accepted: virtEnvName, envName, _ = dlg.getData() args = [ "--name", envName.strip(), "--clone", prefix, ] ok, prefix, interpreter = self.__conda.createCondaEnvironment( args) if ok: ericApp().getObject("VirtualEnvManager").addVirtualEnv( virtEnvName, prefix, interpreter, isConda=True) @pyqtSlot() def __createEnvironment(self): """ Private slot to create a conda environment from a requirements file. """ from .CondaNewEnvironmentDataDialog import ( CondaNewEnvironmentDataDialog) dlg = CondaNewEnvironmentDataDialog(self.tr("Create Environment"), True, self) if dlg.exec() == QDialog.DialogCode.Accepted: virtEnvName, envName, requirements = dlg.getData() args = [ "--name", envName.strip(), "--file", requirements, ] ok, prefix, interpreter = self.__conda.createCondaEnvironment(args) if ok: ericApp().getObject("VirtualEnvManager").addVirtualEnv( virtEnvName, prefix, interpreter, isConda=True) @pyqtSlot() def __deleteEnvironment(self): """ Private slot to delete a conda environment. """ envName = self.environmentsComboBox.currentText() ok = EricMessageBox.yesNo( self, self.tr("Delete Environment"), self.tr("""<p>Shall the environment <b>{0}</b> really be""" """ deleted?</p>""").format(envName) ) if ok: self.__conda.removeCondaEnvironment(name=envName) @pyqtSlot() def __editUserConfiguration(self): """ Private slot to edit the user configuration. """ from QScintilla.MiniEditor import MiniEditor cfgFile = CondaInterface.userConfiguration() if not cfgFile: return if not os.path.exists(cfgFile): self.__conda.writeDefaultConfiguration() # check, if the destination is writeable if not os.access(cfgFile, os.W_OK): EricMessageBox.critical( None, self.tr("Edit Configuration"), self.tr("""The configuration file "{0}" does not exist""" """ or is not writable.""").format(cfgFile)) return self.__editor = MiniEditor(cfgFile, "YAML") self.__editor.show() @pyqtSlot() def __condaConfigure(self): """ Private slot to open the configuration page. """ ericApp().getObject("UserInterface").showPreferences("condaPage") @pyqtSlot() def on_recheckButton_clicked(self): """ Private slot to re-check the availability of conda and adjust the interface if it became available. """ if CondaInterface.isCondaAvailable(): self.__initCondaInterface() self.notAvailableWidget.hide() self.baseWidget.show()