Thu, 14 Sep 2023 15:54:56 +0200
Modified the Pip.getOutdatedPackages() method to keep the UI responsive while pip accesses the internet for outdated packages.
--- 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()
--- 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): """