Modified the Pip.getOutdatedPackages() method to keep the UI responsive while pip accesses the internet for outdated packages. eric7

Thu, 14 Sep 2023 15:54:56 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Thu, 14 Sep 2023 15:54:56 +0200
branch
eric7
changeset 10209
8bb763e85937
parent 10208
d2fb44007ed3
child 10210
7d0e5ca7eb3e

Modified the Pip.getOutdatedPackages() method to keep the UI responsive while pip accesses the internet for outdated packages.

src/eric7/APIs/Python3/eric7.api file | annotate | diff | comparison | revisions
src/eric7/Documentation/Help/source.qch file | annotate | diff | comparison | revisions
src/eric7/Documentation/Help/source.qhp file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.PipInterface.Pip.html file | annotate | diff | comparison | revisions
src/eric7/Documentation/Source/eric7.PipInterface.PipPackagesWidget.html file | annotate | diff | comparison | revisions
src/eric7/PipInterface/Pip.py file | annotate | diff | comparison | revisions
src/eric7/PipInterface/PipPackagesWidget.py file | annotate | diff | comparison | revisions
--- a/src/eric7/APIs/Python3/eric7.api	Wed Sep 13 17:46:12 2023 +0200
+++ b/src/eric7/APIs/Python3/eric7.api	Thu Sep 14 15:54:56 2023 +0200
@@ -3636,7 +3636,7 @@
 eric7.PipInterface.Pip.Pip.getInstalledPackages?4(envName, localPackages=True, notRequired=False, usersite=False)
 eric7.PipInterface.Pip.Pip.getLicenses?4(envName)
 eric7.PipInterface.Pip.Pip.getNetworkAccessManager?4()
-eric7.PipInterface.Pip.Pip.getOutdatedPackages?4(envName, localPackages=True, notRequired=False, usersite=False, interpreter=None, )
+eric7.PipInterface.Pip.Pip.getOutdatedPackages?4(envName, localPackages=True, notRequired=False, usersite=False, interpreter=None, callback=None, )
 eric7.PipInterface.Pip.Pip.getPackageDetails?4(name, version)
 eric7.PipInterface.Pip.Pip.getPackageVersions?4(name)
 eric7.PipInterface.Pip.Pip.getProjectEnvironmentString?4()
Binary file src/eric7/Documentation/Help/source.qch has changed
--- a/src/eric7/Documentation/Help/source.qhp	Wed Sep 13 17:46:12 2023 +0200
+++ b/src/eric7/Documentation/Help/source.qhp	Thu Sep 14 15:54:56 2023 +0200
@@ -12278,6 +12278,8 @@
       <keyword name="Pip (Module)" id="Pip (Module)" ref="eric7.PipInterface.Pip.html" />
       <keyword name="Pip.__checkUpgradeEric" id="Pip.__checkUpgradeEric" ref="eric7.PipInterface.Pip.html#Pip.__checkUpgradeEric" />
       <keyword name="Pip.__checkUpgradePyQt" id="Pip.__checkUpgradePyQt" ref="eric7.PipInterface.Pip.html#Pip.__checkUpgradePyQt" />
+      <keyword name="Pip.__extractOutdatedPackages" id="Pip.__extractOutdatedPackages" ref="eric7.PipInterface.Pip.html#Pip.__extractOutdatedPackages" />
+      <keyword name="Pip.__outdatedFinished" id="Pip.__outdatedFinished" ref="eric7.PipInterface.Pip.html#Pip.__outdatedFinished" />
       <keyword name="Pip.cacheList" id="Pip.cacheList" ref="eric7.PipInterface.Pip.html#Pip.cacheList" />
       <keyword name="Pip.cachePurge" id="Pip.cachePurge" ref="eric7.PipInterface.Pip.html#Pip.cachePurge" />
       <keyword name="Pip.cacheRemove" id="Pip.cacheRemove" ref="eric7.PipInterface.Pip.html#Pip.cacheRemove" />
@@ -12419,6 +12421,7 @@
       <keyword name="PipPackagesWidget.__uninstallRequirements" id="PipPackagesWidget.__uninstallRequirements" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__uninstallRequirements" />
       <keyword name="PipPackagesWidget.__updateActionButtons" id="PipPackagesWidget.__updateActionButtons" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__updateActionButtons" />
       <keyword name="PipPackagesWidget.__updateDepActionButtons" id="PipPackagesWidget.__updateDepActionButtons" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__updateDepActionButtons" />
+      <keyword name="PipPackagesWidget.__updateOutdatedInfo" id="PipPackagesWidget.__updateOutdatedInfo" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__updateOutdatedInfo" />
       <keyword name="PipPackagesWidget.__updateSearchActionButtons" id="PipPackagesWidget.__updateSearchActionButtons" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__updateSearchActionButtons" />
       <keyword name="PipPackagesWidget.__updateSearchButton" id="PipPackagesWidget.__updateSearchButton" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__updateSearchButton" />
       <keyword name="PipPackagesWidget.__updateSearchMoreButton" id="PipPackagesWidget.__updateSearchMoreButton" ref="eric7.PipInterface.PipPackagesWidget.html#PipPackagesWidget.__updateSearchMoreButton" />
--- a/src/eric7/Documentation/Source/eric7.PipInterface.Pip.html	Wed Sep 13 17:46:12 2023 +0200
+++ b/src/eric7/Documentation/Source/eric7.PipInterface.Pip.html	Thu Sep 14 15:54:56 2023 +0200
@@ -67,6 +67,14 @@
 <td>Private method to check, if an upgrade of PyQt packages is attempted.</td>
 </tr>
 <tr>
+<td><a href="#Pip.__extractOutdatedPackages">__extractOutdatedPackages</a></td>
+<td>Private method to extract the outdated packages list out of the process output.</td>
+</tr>
+<tr>
+<td><a href="#Pip.__outdatedFinished">__outdatedFinished</a></td>
+<td>Private method to handle the process finished signal.</td>
+</tr>
+<tr>
 <td><a href="#Pip.cacheList">cacheList</a></td>
 <td>Public method to list files contained in the pip cache.</td>
 </tr>
@@ -264,6 +272,60 @@
 bool
 </dd>
 </dl>
+<a NAME="Pip.__extractOutdatedPackages" ID="Pip.__extractOutdatedPackages"></a>
+<h4>Pip.__extractOutdatedPackages</h4>
+<b>__extractOutdatedPackages</b>(<i>proc</i>)
+
+<p>
+        Private method to extract the outdated packages list out of the process output.
+</p>
+<dl>
+
+<dt><i>proc</i> (QProcess)</dt>
+<dd>
+reference to the process
+</dd>
+</dl>
+<dl>
+<dt>Return:</dt>
+<dd>
+list of tuples containing the package name, installed version
+            and available version
+</dd>
+</dl>
+<dl>
+<dt>Return Type:</dt>
+<dd>
+list of tuple of (str, str, str)
+</dd>
+</dl>
+<a NAME="Pip.__outdatedFinished" ID="Pip.__outdatedFinished"></a>
+<h4>Pip.__outdatedFinished</h4>
+<b>__outdatedFinished</b>(<i>callback, proc, exitCode, exitStatus</i>)
+
+<p>
+        Private method to handle the process finished signal.
+</p>
+<dl>
+
+<dt><i>callback</i> (function)</dt>
+<dd>
+reference to the function to be called with the list of
+            outdated packages
+</dd>
+<dt><i>proc</i> (QProcess)</dt>
+<dd>
+reference to the process
+</dd>
+<dt><i>exitCode</i> (int)</dt>
+<dd>
+exit code of the process
+</dd>
+<dt><i>exitStatus</i> (QProcess.ExitStatus)</dt>
+<dd>
+exit status of the process
+</dd>
+</dl>
 <a NAME="Pip.cacheList" ID="Pip.cacheList"></a>
 <h4>Pip.cacheList</h4>
 <b>cacheList</b>(<i>venvName</i>)
@@ -571,7 +633,7 @@
 </dl>
 <a NAME="Pip.getOutdatedPackages" ID="Pip.getOutdatedPackages"></a>
 <h4>Pip.getOutdatedPackages</h4>
-<b>getOutdatedPackages</b>(<i>envName, localPackages=True, notRequired=False, usersite=False, interpreter=None, </i>)
+<b>getOutdatedPackages</b>(<i>envName, localPackages=True, notRequired=False, usersite=False, interpreter=None, callback=None, </i>)
 
 <p>
         Public method to get the list of outdated packages.
@@ -602,6 +664,11 @@
 path of an interpreter executable. If this is not
             None, it will override the given environment name (defaults to None)
 </dd>
+<dt><i>callback</i> (function)</dt>
+<dd>
+method accepting a list of tuples containing the
+            package name, installed version and available version
+</dd>
 </dl>
 <dl>
 <dt>Return:</dt>
--- a/src/eric7/Documentation/Source/eric7.PipInterface.PipPackagesWidget.html	Wed Sep 13 17:46:12 2023 +0200
+++ b/src/eric7/Documentation/Source/eric7.PipInterface.PipPackagesWidget.html	Thu Sep 14 15:54:56 2023 +0200
@@ -243,6 +243,10 @@
 <td>Private method to set the state of the dependency page action buttons.</td>
 </tr>
 <tr>
+<td><a href="#PipPackagesWidget.__updateOutdatedInfo">__updateOutdatedInfo</a></td>
+<td>Private method to process the list of outdated packages.</td>
+</tr>
+<tr>
 <td><a href="#PipPackagesWidget.__updateSearchActionButtons">__updateSearchActionButtons</a></td>
 <td>Private method to update the action button states of the search widget.</td>
 </tr>
@@ -937,6 +941,21 @@
 <p>
         Private method to set the state of the dependency page action buttons.
 </p>
+<a NAME="PipPackagesWidget.__updateOutdatedInfo" ID="PipPackagesWidget.__updateOutdatedInfo"></a>
+<h4>PipPackagesWidget.__updateOutdatedInfo</h4>
+<b>__updateOutdatedInfo</b>(<i>outdatedPackages</i>)
+
+<p>
+        Private method to process the list of outdated packages.
+</p>
+<dl>
+
+<dt><i>outdatedPackages</i> (list of tuple of (str, str, str))</dt>
+<dd>
+list of tuples containing the package name,
+            installed version and available version
+</dd>
+</dl>
 <a NAME="PipPackagesWidget.__updateSearchActionButtons" ID="PipPackagesWidget.__updateSearchActionButtons"></a>
 <h4>PipPackagesWidget.__updateSearchActionButtons</h4>
 <b>__updateSearchActionButtons</b>(<i></i>)
--- a/src/eric7/PipInterface/Pip.py	Wed Sep 13 17:46:12 2023 +0200
+++ b/src/eric7/PipInterface/Pip.py	Thu Sep 14 15:54:56 2023 +0200
@@ -8,6 +8,7 @@
 """
 
 import contextlib
+import functools
 import json
 import os
 import sys
@@ -684,6 +685,7 @@
         notRequired=False,
         usersite=False,
         interpreter=None,
+        callback=None,
     ):
         """
         Public method to get the list of outdated packages.
@@ -702,6 +704,9 @@
         @param interpreter path of an interpreter executable. If this is not
             None, it will override the given environment name (defaults to None)
         @type str (optional)
+        @param callback method accepting a list of tuples containing the
+            package name, installed version and available version
+        @type function
         @return list of tuples containing the package name, installed version
             and available version
         @rtype list of tuple of (str, str, str)
@@ -731,32 +736,78 @@
                     args += ["--index-url", indexUrl]
 
                 proc = QProcess()
+                if callback:
+                    self.__outdatedProc = proc
+                    proc.finished.connect(
+                        functools.partial(self.__outdatedFinished, callback, proc)
+                    )
+                    proc.start(interpreter, args)
+                    return None
+
                 proc.start(interpreter, args)
                 if proc.waitForStarted(15000) and proc.waitForFinished(30000):
-                    output = str(
-                        proc.readAllStandardOutput(),
-                        Preferences.getSystem("IOEncoding"),
-                        "replace",
-                    ).strip()
-                    if output:
-                        output = output.splitlines()[0]
-                        try:
-                            jsonList = json.loads(output)
-                        except Exception:
-                            jsonList = []
-
-                        for package in jsonList:
-                            if isinstance(package, dict):
-                                packages.append(
-                                    (
-                                        package["name"],
-                                        package["version"],
-                                        package["latest_version"],
-                                    )
-                                )
+                    packages = self.__extractOutdatedPackages(proc)
 
         return packages
 
+    def __extractOutdatedPackages(self, proc):
+        """
+        Private method to extract the outdated packages list out of the process output.
+
+        @param proc reference to the process
+        @type QProcess
+        @return list of tuples containing the package name, installed version
+            and available version
+        @rtype list of tuple of (str, str, str)
+        """
+        packages = []
+
+        output = str(
+            proc.readAllStandardOutput(),
+            Preferences.getSystem("IOEncoding"),
+            "replace",
+        ).strip()
+        if output:
+            output = output.splitlines()[0]
+            try:
+                jsonList = json.loads(output)
+            except Exception:
+                jsonList = []
+
+            for package in jsonList:
+                if isinstance(package, dict):
+                    packages.append(
+                        (
+                            package["name"],
+                            package["version"],
+                            package["latest_version"],
+                        )
+                    )
+
+        return packages
+
+    def __outdatedFinished(self, callback, proc, exitCode, exitStatus):
+        """
+        Private method to handle the process finished signal.
+
+        @param callback reference to the function to be called with the list of
+            outdated packages
+        @type function
+        @param proc reference to the process
+        @type QProcess
+        @param exitCode exit code of the process
+        @type int
+        @param exitStatus exit status of the process
+        @type QProcess.ExitStatus
+        """
+        packages = (
+            self.__extractOutdatedPackages(proc)
+            if exitStatus == QProcess.ExitStatus.NormalExit and exitCode == 0
+            else []
+        )
+        callback(packages)
+        self.__outdatedProc = None
+
     def checkPackagesOutdated(self, packageStarts, envName, interpreter=None):
         """
         Public method to check, if groups of packages are outdated.
--- a/src/eric7/PipInterface/PipPackagesWidget.py	Wed Sep 13 17:46:12 2023 +0200
+++ b/src/eric7/PipInterface/PipPackagesWidget.py	Thu Sep 14 15:54:56 2023 +0200
@@ -292,10 +292,10 @@
         @type bool
         """
         if not shutdown:
+            # the project entry is always at index 1
             if self.environmentsComboBox.currentIndex() == 1:
                 self.environmentsComboBox.setCurrentIndex(0)
 
-            # the project entry is always at index 1
             self.environmentsComboBox.removeItem(1)
 
     def __populateEnvironments(self):
@@ -441,7 +441,6 @@
 
                 with EricOverrideCursor():
                     # 1. populate with installed packages
-                    self.packagesList.setUpdatesEnabled(False)
                     installedPackages = self.__pip.getInstalledPackages(
                         venvName,
                         localPackages=self.localCheckBox.isChecked(),
@@ -450,41 +449,60 @@
                     )
                     for package, version in installedPackages:
                         QTreeWidgetItem(self.packagesList, [package, version, "", ""])
-                    self.packagesList.setUpdatesEnabled(True)
+                    self.packagesList.sortItems(
+                        PipPackagesWidget.PackageColumn, Qt.SortOrder.AscendingOrder
+                    )
+                    self.packagesList.resizeColumnToContents(
+                        PipPackagesWidget.PackageColumn
+                    )
+                    self.packagesList.resizeColumnToContents(
+                        PipPackagesWidget.InstalledVersionColumn
+                    )
+                    QApplication.processEvents()
+
+                    # 2. update with vulnerability information
+                    if self.vulnerabilityCheckBox.isChecked():
+                        self.__updateVulnerabilityData()
+                        self.packagesList.resizeColumnToContents(
+                            PipPackagesWidget.VulnerabilityColumn
+                        )
                     self.statusLabel.setText(self.tr("Getting outdated packages..."))
                     QApplication.processEvents()
 
-                    # 2. update with update information
-                    self.packagesList.setUpdatesEnabled(False)
-                    outdatedPackages = self.__pip.getOutdatedPackages(
+                    # 3. update with update information
+                    self.__pip.getOutdatedPackages(
                         venvName,
                         localPackages=self.localCheckBox.isChecked(),
                         notRequired=self.notRequiredCheckBox.isChecked(),
                         usersite=self.userCheckBox.isChecked(),
+                        callback=self.__updateOutdatedInfo,
                     )
-                    for package, _version, latest in outdatedPackages:
-                        items = self.packagesList.findItems(
-                            package,
-                            Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive,
-                        )
-                        if items:
-                            itm = items[0]
-                            itm.setText(
-                                PipPackagesWidget.AvailableVersionColumn, latest
-                            )
-                    QApplication.processEvents()
+
+        else:
+            self.__updateActionButtons()
+            self.__updateSearchActionButtons()
+            self.__updateSearchButton()
+            self.__updateSearchMoreButton(False)
+
+    def __updateOutdatedInfo(self, outdatedPackages):
+        """
+        Private method to process the list of outdated packages.
 
-                    # 3. update with vulnerability information
-                    if self.vulnerabilityCheckBox.isChecked():
-                        self.__updateVulnerabilityData()
-
-                    self.packagesList.sortItems(
-                        PipPackagesWidget.PackageColumn, Qt.SortOrder.AscendingOrder
-                    )
-                    for col in range(self.packagesList.columnCount()):
-                        self.packagesList.resizeColumnToContents(col)
-                    self.packagesList.setUpdatesEnabled(True)
-                self.statusLabel.hide()
+        @param outdatedPackages list of tuples containing the package name,
+            installed version and available version
+        @type list of tuple of (str, str, str)
+        """
+        for package, _version, latest in outdatedPackages:
+            items = self.packagesList.findItems(
+                package,
+                Qt.MatchFlag.MatchExactly | Qt.MatchFlag.MatchCaseSensitive,
+            )
+            if items:
+                items[0].setText(PipPackagesWidget.AvailableVersionColumn, latest)
+        self.packagesList.resizeColumnToContents(
+            PipPackagesWidget.AvailableVersionColumn
+        )
+        self.statusLabel.hide()
 
         self.__updateActionButtons()
         self.__updateSearchActionButtons()
@@ -611,6 +629,8 @@
             self.vulnerabilitiesInfoWidget.clear()
             self.infoWidget.tabBar().hide()
 
+        self.__updateActionButtons()
+
     @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
     def on_packagesList_currentItemChanged(self, curr, prev):
         """

eric ide

mercurial