Tue, 05 Feb 2019 19:58:26 +0100
Conda, CondaPackagesWidget: continued implementing list functionality.
--- a/CondaInterface/Conda.py Tue Feb 05 19:39:42 2019 +0100 +++ b/CondaInterface/Conda.py Tue Feb 05 19:58:26 2019 +0100 @@ -41,6 +41,10 @@ self.__ui = parent + ####################################################################### + ## environment related methods below + ####################################################################### + def createCondaEnvironment(self, arguments): """ Public method to create a conda environment. @@ -162,3 +166,276 @@ return False return jsonDict["success"] + + def getCondaEnvironmentsList(self, listPrefixes=False): + """ + Public method to get a list of all Conda environments. + + @param listPrefixes flag indicating to return prefixes instead of names + @type bool + @return list of environment names or prefixes + @rtype list of str + """ + # TODO: return environment name and prefix + # TODO: return root environment only, if writable ('root_prefix', 'root_writable') + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + environmentsList = [] + + proc = QProcess() + proc.start(exe, ["info", "--json"]) + if proc.waitForStarted(15000): + if proc.waitForFinished(15000): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + jsonDict = json.loads(output) + except Exception: + jsonDict = {} + + if "envs" in jsonDict: + if listPrefixes: + environmentsList = jsonDict["envs"][:] + else: + environmentsList = [ + os.path.basename(e) for e in jsonDict["envs"]] + + return environmentsList + + ####################################################################### + ## package related methods below + ####################################################################### + + def getInstalledPackages(self, name="", prefix=""): + """ + Public method to get a list of installed packages of a conda + environment. + + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return list of installed packages. Each entry is a tuple containing + the package name, version and build. + @rtype list of tuples of (str, str, str) + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + args = [ + "list", + "--json", + ] + if name: + args.extend(["--name", name]) + elif prefix: + args.extend(["--prefix", prefix]) + + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + packages = [] + + proc = QProcess() + proc.start(exe, args) + if proc.waitForStarted(15000): + if proc.waitForFinished(15000): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + jsonList = json.loads(output) + except Exception: + jsonList = [] + + for package in jsonList: + if isinstance(package, dict): + packages.append(( + package["name"], + package["version"], + package["build_string"] + )) + else: + packages.append(tuple(package.rsplit("-", 2))) + + return packages + + def getUpdateablePackages(self, name="", prefix=""): + """ + Public method to get a list of updateable packages of a conda + environment. + + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return list of installed packages. Each entry is a tuple containing + the package name, version and build. + @rtype list of tuples of (str, str, str) + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + args = [ + "update", + "--json", + "--quiet", + "--all", + "--dry-run", + ] + if name: + args.extend(["--name", name]) + elif prefix: + args.extend(["--prefix", prefix]) + + exe = Preferences.getConda("CondaExecutable") + if not exe: + exe = "conda" + + packages = [] + + proc = QProcess() + proc.start(exe, args) + if proc.waitForStarted(15000): + if proc.waitForFinished(15000): + output = str(proc.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace').strip() + try: + jsonDict = json.loads(output) + except Exception: + jsonDict = {} + + if "actions" in jsonDict and "LINK" in jsonDict["actions"]: + for linkEntry in jsonDict["actions"]["LINK"]: + if isinstance(linkEntry, dict): + packages.append(( + linkEntry["name"], + linkEntry["version"], + linkEntry["build_string"] + )) + else: + package = linkEntry.split()[0] + packages.append(tuple(package.rsplit("-", 2))) + + return packages + + def updatePackages(self, packages, name="", prefix=""): + """ + Public method to update packages of a conda environment. + + @param packages list of package names to be updated + @type list of str + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return list of installed packages. Each entry is a tuple containing + the package name, version and build. + @rtype list of tuples of (str, str, str) + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + # TODO: not implemented yet + + def updateAllPackages(self, name="", prefix=""): + """ + Public method to update all packages of a conda environment. + + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return list of installed packages. Each entry is a tuple containing + the package name, version and build. + @rtype list of tuples of (str, str, str) + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + # TODO: not implemented yet + + def installPackages(self, packages, name="", prefix=""): + """ + Public method to install packages into a conda environment. + + @param packages list of package names to be installed + @type list of str + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return list of installed packages. Each entry is a tuple containing + the package name, version and build. + @rtype list of tuples of (str, str, str) + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + # TODO: not implemented yet + + def uninstallPackages(self, packages, name="", prefix=""): + """ + Public method to uninstall packages of a conda environment. + + @param packages list of package names to be uninstalled + @type list of str + @param name name of the environment + @type str + @param prefix prefix of the environment + @type str + @return list of installed packages. Each entry is a tuple containing + the package name, version and build. + @rtype list of tuples of (str, str, str) + @rtype bool + @exception RuntimeError raised to indicate an error in parameters + + Note: only one of name or prefix must be given. + """ + if name and prefix: + raise RuntimeError("Only one of 'name' or 'prefix' must be given.") + + if not name and not prefix: + raise RuntimeError("One of 'name' or 'prefix' must be given.") + + # TODO: not implemented yet
--- a/CondaInterface/CondaPackagesWidget.py Tue Feb 05 19:39:42 2019 +0100 +++ b/CondaInterface/CondaPackagesWidget.py Tue Feb 05 19:58:26 2019 +0100 @@ -9,10 +9,10 @@ from __future__ import unicode_literals -import os - from PyQt5.QtCore import pyqtSlot, Qt -from PyQt5.QtWidgets import QWidget, QToolButton, QMenu +from PyQt5.QtGui import QCursor +from PyQt5.QtWidgets import QWidget, QToolButton, QMenu, QTreeWidgetItem, \ + QApplication from .Ui_CondaPackagesWidget import Ui_CondaPackagesWidget @@ -23,16 +23,20 @@ """ Class implementing the conda packages management widget. """ - def __init__(self, parent=None): + 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(CondaPackagesWidget, self).__init__(parent) self.setupUi(self) + self.__conda = conda + self.condaMenuButton.setObjectName( "navigation_supermenu_button") self.condaMenuButton.setIcon(UI.PixmapCache.getIcon("superMenu.png")) @@ -45,19 +49,14 @@ self.__initCondaMenu() self.__populateEnvironments() + self.__updateActionButtons() def __populateEnvironments(self): """ Private method to get a list of environments and populate the selector. """ - # TODO: replace this by the real stuff - envs = [ - "path/to/envs/sqlite", - "path/to/envs/pyqt4", - "path/to/envs/pyqt5", - ] - - environmentNames = [os.path.basename(e) for e in envs] + # TODO: populate with name and prefix + environmentNames = [""] + self.__conda.getCondaEnvironmentsList() self.environmentsComboBox.addItems(sorted(environmentNames)) def __initCondaMenu(self): @@ -71,32 +70,104 @@ self.condaMenuButton.setMenu(self.__condaMenu) - @pyqtSlot(str) - def on_environmentsComboBox_activated(self, p0): + 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 """ - Slot documentation goes here. + updateableItems = [] + for index in range(self.packagesList.topLevelItemCount()): + itm = self.packagesList.topLevelItem(index) + if itm.text(2): + updateableItems.append(itm) - @param p0 DESCRIPTION + 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())) + + # TODO: change to int = index + @pyqtSlot(str) + def on_environmentsComboBox_currentIndexChanged(self, name): + """ + Private slot handling the selection of a conda environment. + + @param name name of the selected conda environment name @type str """ - # TODO: not implemented yet - raise NotImplementedError + self.packagesList.clear() + if name: + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QApplication.processEvents() + + # 1. populate with installed packages + # TODO: use prefix + installedPackages = self.__conda.getInstalledPackages(name=name) + for package, version, build_ in installedPackages: + QTreeWidgetItem(self.packagesList, [package, version]) + + # 2. update with update information + updateablePackages = self.__conda.getUpdateablePackages(name=name) + for package, version, build_ in updateablePackages: + items = self.packagesList.findItems( + package, Qt.MatchExactly | Qt.MatchCaseSensitive) + if items: + items[0].setText(2, version) + + QApplication.restoreOverrideCursor() + + self.__updateActionButtons() @pyqtSlot() def on_packagesList_itemSelectionChanged(self): """ - Slot documentation goes here. + Private slot to handle the selection of some items.. """ - # TODO: not implemented yet - raise NotImplementedError + self.__updateActionButtons() @pyqtSlot() def on_refreshButton_clicked(self): """ - Slot documentation goes here. + Private slot to refresh the display. """ - # TODO: not implemented yet - raise NotImplementedError + currentEnvironment = self.environmentsComboBox.currentText() + self.environmentsComboBox.clear() + self.packagesList.clear() + + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QApplication.processEvents() + + self.__populateEnvironments() + + index = self.environmentsComboBox.findText( + currentEnvironment, Qt.MatchExactly | Qt.MatchCaseSensitive) + if index != -1: + self.environmentsComboBox.setCurrentIndex(index) + + QApplication.restoreOverrideCursor() + self.__updateActionButtons() @pyqtSlot() def on_upgradeButton_clicked(self):
--- a/UI/UserInterface.py Tue Feb 05 19:39:42 2019 +0100 +++ b/UI/UserInterface.py Tue Feb 05 19:58:26 2019 +0100 @@ -840,6 +840,14 @@ UI.PixmapCache.getIcon("debugViewer.png"), self.tr("Debug-Viewer")) + # Create the conda package manager + logging.debug("Creating Conda Package Manager...") + from CondaInterface.CondaPackagesWidget import CondaPackagesWidget + self.condaWidget = CondaPackagesWidget(self.condaInterface) + self.rToolbox.addItem(self.condaWidget, + UI.PixmapCache.getIcon("miniconda.png"), + self.tr("Conda")) + if Preferences.getUI("ShowCooperation"): # Create the chat part of the user interface logging.debug("Creating Chat Widget...") @@ -984,7 +992,7 @@ # Create the conda package manager logging.debug("Creating Conda Package Manager...") from CondaInterface.CondaPackagesWidget import CondaPackagesWidget - self.condaWidget = CondaPackagesWidget() + self.condaWidget = CondaPackagesWidget(self.condaInterface) self.rightSidebar.addTab( self.condaWidget, UI.PixmapCache.getIcon("miniconda.png"), self.tr("Conda"))