Conda, CondaPackagesWidget: continued implementing list functionality. conda

Tue, 05 Feb 2019 19:58:26 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 05 Feb 2019 19:58:26 +0100
branch
conda
changeset 6701
af29cf6ca309
parent 6700
2a607332ccf8
child 6704
772563b2eb0a

Conda, CondaPackagesWidget: continued implementing list functionality.

CondaInterface/Conda.py file | annotate | diff | comparison | revisions
CondaInterface/CondaPackagesWidget.py file | annotate | diff | comparison | revisions
UI/UserInterface.py file | annotate | diff | comparison | revisions
--- 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"))

eric ide

mercurial