Sun, 15 Sep 2024 16:56:58 +0200
Added a QProcess derived class with timeout and changed the pip interface to use that class.
--- a/eric7.epj Fri Sep 06 14:56:18 2024 +0200 +++ b/eric7.epj Sun Sep 15 16:56:58 2024 +0200 @@ -1178,6 +1178,7 @@ "src/eric7/DocumentationTools/TemplatesListsStyleCSS.py", "src/eric7/DocumentationTools/__init__.py", "src/eric7/EricCore/EricFileSystemWatcher.py", + "src/eric7/EricCore/EricProcess.py", "src/eric7/EricCore/EricStdRedirector.py", "src/eric7/EricCore/EricTreeSortFilterProxyModel.py", "src/eric7/EricCore/__init__.py",
--- a/src/eric7/APIs/Python3/eric7.api Fri Sep 06 14:56:18 2024 +0200 +++ b/src/eric7/APIs/Python3/eric7.api Sun Sep 15 16:56:58 2024 +0200 @@ -1108,6 +1108,12 @@ eric7.EricCore.EricFileSystemWatcher._EricFileSystemEventHandler?2(parent=None) eric7.EricCore.EricFileSystemWatcher._GlobalFileSystemWatcher?8 eric7.EricCore.EricFileSystemWatcher.instance?4() +eric7.EricCore.EricProcess.EricProcess.failed?7 +eric7.EricCore.EricProcess.EricProcess.succeeded?7 +eric7.EricCore.EricProcess.EricProcess.timedOut?4() +eric7.EricCore.EricProcess.EricProcess.timeout?7 +eric7.EricCore.EricProcess.EricProcess.timeoutInterval?4() +eric7.EricCore.EricProcess.EricProcess?1(timeout=30000, parent=None) eric7.EricCore.EricStdRedirector.EricStdRedirector.encoding?4() eric7.EricCore.EricStdRedirector.EricStdRedirector.flush?4() eric7.EricCore.EricStdRedirector.EricStdRedirector.isatty?4() @@ -3777,6 +3783,7 @@ eric7.PipInterface.Pip.Pip.repairPip?4(venvName) eric7.PipInterface.Pip.Pip.runProcess?4(args, interpreter) eric7.PipInterface.Pip.Pip.showCacheInfo?4(venvName) +eric7.PipInterface.Pip.Pip.shutdown?4() eric7.PipInterface.Pip.Pip.uninstallPackages?4(packages, venvName) eric7.PipInterface.Pip.Pip.uninstallPyprojectDependencies?4(venvName) eric7.PipInterface.Pip.Pip.uninstallRequirements?4(venvName) @@ -3884,6 +3891,7 @@ eric7.PipInterface.PipPackagesWidget.PypiSearchResultsParser.handle_endtag?4(_tag) eric7.PipInterface.PipPackagesWidget.PypiSearchResultsParser.handle_starttag?4(tag, attrs) eric7.PipInterface.PipPackagesWidget.PypiSearchResultsParser?1(data) +eric7.PipInterface.PipPackagesWindow.PipPackagesWindow.closeEvent?4(evt) eric7.PipInterface.PipPackagesWindow.PipPackagesWindow?1(parent=None) eric7.PipInterface.PipVulnerabilityChecker.Package.name?7 eric7.PipInterface.PipVulnerabilityChecker.Package.version?7
--- a/src/eric7/APIs/Python3/eric7.bas Fri Sep 06 14:56:18 2024 +0200 +++ b/src/eric7/APIs/Python3/eric7.bas Sun Sep 15 16:56:58 2024 +0200 @@ -297,6 +297,7 @@ EricPathPickerDialog QDialog EricPathPickerModes enum.Enum EricPlainTextDialog QDialog Ui_EricPlainTextDialog +EricProcess QProcess EricProcessDialog QDialog Ui_EricProcessDialog EricProgressDialog QProgressDialog EricProxyStyle QProxyStyle
--- a/src/eric7/Documentation/Help/source.qhp Fri Sep 06 14:56:18 2024 +0200 +++ b/src/eric7/Documentation/Help/source.qhp Sun Sep 15 16:56:58 2024 +0200 @@ -109,6 +109,7 @@ </section> <section title="eric7.EricCore" ref="index-eric7.EricCore.html"> <section title="eric7.EricCore.EricFileSystemWatcher" ref="eric7.EricCore.EricFileSystemWatcher.html" /> + <section title="eric7.EricCore.EricProcess" ref="eric7.EricCore.EricProcess.html" /> <section title="eric7.EricCore.EricStdRedirector" ref="eric7.EricCore.EricStdRedirector.html" /> <section title="eric7.EricCore.EricTreeSortFilterProxyModel" ref="eric7.EricCore.EricTreeSortFilterProxyModel.html" /> </section> @@ -5993,6 +5994,14 @@ <keyword name="EricPlugin.pytest_sessionfinish" id="EricPlugin.pytest_sessionfinish" ref="eric7.Testing.Interfaces.PytestRunner.html#EricPlugin.pytest_sessionfinish" /> <keyword name="EricPlugin.pytest_sessionstart" id="EricPlugin.pytest_sessionstart" ref="eric7.Testing.Interfaces.PytestRunner.html#EricPlugin.pytest_sessionstart" /> <keyword name="EricPluginWizard (Package)" id="EricPluginWizard (Package)" ref="index-eric7.Plugins.WizardPlugins.EricPluginWizard.html" /> + <keyword name="EricProcess" id="EricProcess" ref="eric7.EricCore.EricProcess.html#EricProcess" /> + <keyword name="EricProcess (Constructor)" id="EricProcess (Constructor)" ref="eric7.EricCore.EricProcess.html#EricProcess.__init__" /> + <keyword name="EricProcess (Module)" id="EricProcess (Module)" ref="eric7.EricCore.EricProcess.html" /> + <keyword name="EricProcess.__finished" id="EricProcess.__finished" ref="eric7.EricCore.EricProcess.html#EricProcess.__finished" /> + <keyword name="EricProcess.__started" id="EricProcess.__started" ref="eric7.EricCore.EricProcess.html#EricProcess.__started" /> + <keyword name="EricProcess.__timeout" id="EricProcess.__timeout" ref="eric7.EricCore.EricProcess.html#EricProcess.__timeout" /> + <keyword name="EricProcess.timedOut" id="EricProcess.timedOut" ref="eric7.EricCore.EricProcess.html#EricProcess.timedOut" /> + <keyword name="EricProcess.timeoutInterval" id="EricProcess.timeoutInterval" ref="eric7.EricCore.EricProcess.html#EricProcess.timeoutInterval" /> <keyword name="EricProcessDialog" id="EricProcessDialog" ref="eric7.EricWidgets.EricProcessDialog.html#EricProcessDialog" /> <keyword name="EricProcessDialog (Constructor)" id="EricProcessDialog (Constructor)" ref="eric7.EricWidgets.EricProcessDialog.html#EricProcessDialog.__init__" /> <keyword name="EricProcessDialog (Module)" id="EricProcessDialog (Module)" ref="eric7.EricWidgets.EricProcessDialog.html" /> @@ -12904,6 +12913,7 @@ <keyword name="Pip.repairPip" id="Pip.repairPip" ref="eric7.PipInterface.Pip.html#Pip.repairPip" /> <keyword name="Pip.runProcess" id="Pip.runProcess" ref="eric7.PipInterface.Pip.html#Pip.runProcess" /> <keyword name="Pip.showCacheInfo" id="Pip.showCacheInfo" ref="eric7.PipInterface.Pip.html#Pip.showCacheInfo" /> + <keyword name="Pip.shutdown" id="Pip.shutdown" ref="eric7.PipInterface.Pip.html#Pip.shutdown" /> <keyword name="Pip.uninstallPackages" id="Pip.uninstallPackages" ref="eric7.PipInterface.Pip.html#Pip.uninstallPackages" /> <keyword name="Pip.uninstallPyprojectDependencies" id="Pip.uninstallPyprojectDependencies" ref="eric7.PipInterface.Pip.html#Pip.uninstallPyprojectDependencies" /> <keyword name="Pip.uninstallRequirements" id="Pip.uninstallRequirements" ref="eric7.PipInterface.Pip.html#Pip.uninstallRequirements" /> @@ -13072,6 +13082,7 @@ <keyword name="PipPackagesWindow" id="PipPackagesWindow" ref="eric7.PipInterface.PipPackagesWindow.html#PipPackagesWindow" /> <keyword name="PipPackagesWindow (Constructor)" id="PipPackagesWindow (Constructor)" ref="eric7.PipInterface.PipPackagesWindow.html#PipPackagesWindow.__init__" /> <keyword name="PipPackagesWindow (Module)" id="PipPackagesWindow (Module)" ref="eric7.PipInterface.PipPackagesWindow.html" /> + <keyword name="PipPackagesWindow.closeEvent" id="PipPackagesWindow.closeEvent" ref="eric7.PipInterface.PipPackagesWindow.html#PipPackagesWindow.closeEvent" /> <keyword name="PipPage" id="PipPage" ref="eric7.Preferences.ConfigurationPages.PipPage.html#PipPage" /> <keyword name="PipPage (Constructor)" id="PipPage (Constructor)" ref="eric7.Preferences.ConfigurationPages.PipPage.html#PipPage.__init__" /> <keyword name="PipPage (Module)" id="PipPage (Module)" ref="eric7.Preferences.ConfigurationPages.PipPage.html" /> @@ -21384,6 +21395,7 @@ <file>eric7.DocumentationTools.QtHelpGenerator.html</file> <file>eric7.DocumentationTools.TemplatesListsStyleCSS.html</file> <file>eric7.EricCore.EricFileSystemWatcher.html</file> + <file>eric7.EricCore.EricProcess.html</file> <file>eric7.EricCore.EricStdRedirector.html</file> <file>eric7.EricCore.EricTreeSortFilterProxyModel.html</file> <file>eric7.EricGraphics.EricArrowItem.html</file>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/Documentation/Source/eric7.EricCore.EricProcess.html Sun Sep 15 16:56:58 2024 +0200 @@ -0,0 +1,194 @@ +<!DOCTYPE html> +<html><head> +<title>eric7.EricCore.EricProcess</title> +<meta charset="UTF-8"> +<link rel="stylesheet" href="styles.css"> +</head> +<body> +<a NAME="top" ID="top"></a> +<h1>eric7.EricCore.EricProcess</h1> +<p> +Module implementing a QProcess derived class with a timeout and convenience signals. +</p> + +<h3>Global Attributes</h3> +<table> +<tr><td>None</td></tr> +</table> + +<h3>Classes</h3> +<table> +<tr> +<td><a href="#EricProcess">EricProcess</a></td> +<td>Class implementing a QProcess derived class with a timeout and convenience signals succeeded and failed.</td> +</tr> +</table> + +<h3>Functions</h3> +<table> +<tr><td>None</td></tr> +</table> + +<hr /> +<hr /> +<a NAME="EricProcess" ID="EricProcess"></a> +<h2>EricProcess</h2> +<p> + Class implementing a QProcess derived class with a timeout and convenience signals + succeeded and failed. +</p> + +<h3>Signals</h3> +<dl> + +<dt>failed()</dt> +<dd> +emitted to indicate a process failure +</dd> +<dt>succeeded()</dt> +<dd> +emitted to indicate that the process finished successfully +</dd> +<dt>timeout()</dt> +<dd> +emitted to indicate the expiry of the configured timeout value +</dd> +</dl> +<h3>Derived from</h3> +QProcess +<h3>Class Attributes</h3> +<table> +<tr><td>None</td></tr> +</table> + +<h3>Class Methods</h3> +<table> +<tr><td>None</td></tr> +</table> + +<h3>Methods</h3> +<table> +<tr> +<td><a href="#EricProcess.__init__">EricProcess</a></td> +<td>Constructor</td> +</tr> +<tr> +<td><a href="#EricProcess.__finished">__finished</a></td> +<td>Private slot handling the end of the process.</td> +</tr> +<tr> +<td><a href="#EricProcess.__started">__started</a></td> +<td>Private slot handling the process start.</td> +</tr> +<tr> +<td><a href="#EricProcess.__timeout">__timeout</a></td> +<td>Private slot to handle the timer interval exoiration.</td> +</tr> +<tr> +<td><a href="#EricProcess.timedOut">timedOut</a></td> +<td>Public method to test, if the process timed out.</td> +</tr> +<tr> +<td><a href="#EricProcess.timeoutInterval">timeoutInterval</a></td> +<td>Public method to get the process timeout interval.</td> +</tr> +</table> + +<h3>Static Methods</h3> +<table> +<tr><td>None</td></tr> +</table> + + +<a NAME="EricProcess.__init__" ID="EricProcess.__init__"></a> +<h4>EricProcess (Constructor)</h4> +<b>EricProcess</b>(<i>timeout=30000, parent=None</i>) +<p> + Constructor +</p> + +<dl> + +<dt><i>timeout</i> (int (optional))</dt> +<dd> +timeout value in milliseconds. If the process does not finish + within this interval, it is killed. (defaults to 30000) +</dd> +<dt><i>parent</i> (QObject (optional))</dt> +<dd> +reference to the parent object (defaults to None) +</dd> +</dl> +<a NAME="EricProcess.__finished" ID="EricProcess.__finished"></a> +<h4>EricProcess.__finished</h4> +<b>__finished</b>(<i>exitCode, exitStatus</i>) +<p> + Private slot handling the end of the process. +</p> + +<dl> + +<dt><i>exitCode</i> (int)</dt> +<dd> +exit code of the process (0 = success) +</dd> +<dt><i>exitStatus</i> (QProcess.ExitStatus)</dt> +<dd> +exit status of the process +</dd> +</dl> +<a NAME="EricProcess.__started" ID="EricProcess.__started"></a> +<h4>EricProcess.__started</h4> +<b>__started</b>(<i></i>) +<p> + Private slot handling the process start. +</p> + +<a NAME="EricProcess.__timeout" ID="EricProcess.__timeout"></a> +<h4>EricProcess.__timeout</h4> +<b>__timeout</b>(<i></i>) +<p> + Private slot to handle the timer interval exoiration. +</p> + +<a NAME="EricProcess.timedOut" ID="EricProcess.timedOut"></a> +<h4>EricProcess.timedOut</h4> +<b>timedOut</b>(<i></i>) +<p> + Public method to test, if the process timed out. +</p> + +<dl> +<dt>Return:</dt> +<dd> +flag indicating a timeout +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +bool +</dd> +</dl> +<a NAME="EricProcess.timeoutInterval" ID="EricProcess.timeoutInterval"></a> +<h4>EricProcess.timeoutInterval</h4> +<b>timeoutInterval</b>(<i></i>) +<p> + Public method to get the process timeout interval. +</p> + +<dl> +<dt>Return:</dt> +<dd> +process timeout interval in milliseconds +</dd> +</dl> +<dl> +<dt>Return Type:</dt> +<dd> +int +</dd> +</dl> +<div align="right"><a href="#top">Up</a></div> +<hr /> +</body></html>
--- a/src/eric7/Documentation/Source/eric7.PipInterface.Pip.html Fri Sep 06 14:56:18 2024 +0200 +++ b/src/eric7/Documentation/Source/eric7.PipInterface.Pip.html Sun Sep 15 16:56:58 2024 +0200 @@ -195,6 +195,10 @@ <td>Public method to show some information about the pip cache.</td> </tr> <tr> +<td><a href="#Pip.shutdown">shutdown</a></td> +<td>Public method to perform shutdown actions.</td> +</tr> +<tr> <td><a href="#Pip.uninstallPackages">uninstallPackages</a></td> <td>Public method to uninstall the given list of packages.</td> </tr> @@ -1090,6 +1094,13 @@ name of the virtual environment to be used </dd> </dl> +<a NAME="Pip.shutdown" ID="Pip.shutdown"></a> +<h4>Pip.shutdown</h4> +<b>shutdown</b>(<i></i>) +<p> + Public method to perform shutdown actions. +</p> + <a NAME="Pip.uninstallPackages" ID="Pip.uninstallPackages"></a> <h4>Pip.uninstallPackages</h4> <b>uninstallPackages</b>(<i>packages, venvName</i>)
--- a/src/eric7/Documentation/Source/eric7.PipInterface.PipPackagesWindow.html Fri Sep 06 14:56:18 2024 +0200 +++ b/src/eric7/Documentation/Source/eric7.PipInterface.PipPackagesWindow.html Sun Sep 15 16:56:58 2024 +0200 @@ -55,6 +55,10 @@ <td><a href="#PipPackagesWindow.__init__">PipPackagesWindow</a></td> <td>Constructor</td> </tr> +<tr> +<td><a href="#PipPackagesWindow.closeEvent">closeEvent</a></td> +<td>Protected method handling a close event.</td> +</tr> </table> <h3>Static Methods</h3> @@ -77,6 +81,20 @@ reference to the parent widget </dd> </dl> +<a NAME="PipPackagesWindow.closeEvent" ID="PipPackagesWindow.closeEvent"></a> +<h4>PipPackagesWindow.closeEvent</h4> +<b>closeEvent</b>(<i>evt</i>) +<p> + Protected method handling a close event. +</p> + +<dl> + +<dt><i>evt</i> (QCloseEvent)</dt> +<dd> +reference to the close event object +</dd> +</dl> <div align="right"><a href="#top">Up</a></div> <hr /> </body></html>
--- a/src/eric7/Documentation/Source/index-eric7.EricCore.html Fri Sep 06 14:56:18 2024 +0200 +++ b/src/eric7/Documentation/Source/index-eric7.EricCore.html Sun Sep 15 16:56:58 2024 +0200 @@ -23,6 +23,10 @@ <td>Module implementing a QFileSystemWatcher replacement based on the 'watchdog' package.</td> </tr> <tr> +<td><a href="eric7.EricCore.EricProcess.html">EricProcess</a></td> +<td>Module implementing a QProcess derived class with a timeout and convenience signals.</td> +</tr> +<tr> <td><a href="eric7.EricCore.EricStdRedirector.html">EricStdRedirector</a></td> <td>Module implementing a redirector for stderr and stdout to be able to send data written to these streams via Qt signals.</td> </tr>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/EricCore/EricProcess.py Sun Sep 15 16:56:58 2024 +0200 @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a QProcess derived class with a timeout and convenience signals. +""" + +from PyQt6.QtCore import QProcess, QTimer, pyqtSignal, pyqtSlot + + +class EricProcess(QProcess): + """ + Class implementing a QProcess derived class with a timeout and convenience signals + succeeded and failed. + + @signal failed() emitted to indicate a process failure + @signal succeeded() emitted to indicate that the process finished successfully + @signal timeout() emitted to indicate the expiry of the configured timeout value + """ + + failed = pyqtSignal() + succeeded = pyqtSignal() + timeout = pyqtSignal() + + def __init__(self, timeout=30000, parent=None): + """ + Constructor + + @param timeout timeout value in milliseconds. If the process does not finish + within this interval, it is killed. (defaults to 30000) + @type int (optional) + @param parent reference to the parent object (defaults to None) + @type QObject (optional) + """ + super().__init__(parent=parent) + + self.started.connect(self.__started) + self.finished.connect(self.__finished) + + self.__timeoutTimer = QTimer(self) + self.__timeoutTimer.setInterval(timeout) + self.__timeoutTimer.timeout.connect(self.__timeout) + + self.__timedOut = False + + def timedOut(self): + """ + Public method to test, if the process timed out. + + @return flag indicating a timeout + @rtype bool + """ + return self.__timedOut + + def timeoutInterval(self): + """ + Public method to get the process timeout interval. + + @return process timeout interval in milliseconds + @rtype int + """ + return self.__timeoutTimer.interval() + + @pyqtSlot() + def __timeout(self): + """ + Private slot to handle the timer interval exoiration. + """ + self.__timeoutTimer.stop() + self.__timedOut = True + self.kill() + + self.timeout.emit() + + @pyqtSlot() + def __started(self): + """ + Private slot handling the process start. + """ + self.__timedOut = False + self.__timeoutTimer.start() + + @pyqtSlot(int, QProcess.ExitStatus) + def __finished(self, exitCode, exitStatus): + """ + Private slot handling the end of the process. + + @param exitCode exit code of the process (0 = success) + @type int + @param exitStatus exit status of the process + @type QProcess.ExitStatus + """ + self.__timeoutTimer.stop() + + if exitStatus == QProcess.ExitStatus.CrashExit or exitCode != 0: + self.failed.emit() + else: + self.succeeded.emit()
--- a/src/eric7/PipInterface/Pip.py Fri Sep 06 14:56:18 2024 +0200 +++ b/src/eric7/PipInterface/Pip.py Sun Sep 15 16:56:58 2024 +0200 @@ -26,6 +26,7 @@ from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit from eric7 import Preferences +from eric7.EricCore.EricProcess import EricProcess from eric7.EricNetwork.EricNetworkProxyFactory import ( EricNetworkProxyFactory, proxyAuthenticationRequired, @@ -86,6 +87,8 @@ ) self.__replies = [] + self.__outdatedProc = None + self.__vulnerabilityChecker = PipVulnerabilityChecker(self, self) def getNetworkAccessManager(self): @@ -106,6 +109,14 @@ """ return self.__vulnerabilityChecker + def shutdown(self): + """ + Public method to perform shutdown actions. + """ + if self.__outdatedProc is not None: + self.__outdatedProc.kill() # end the process forcefully + self.__outdatedProc = None + ########################################################################## ## Methods below implement some utility functions ########################################################################## @@ -925,8 +936,12 @@ indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" args += ["--index-url", indexUrl] - proc = QProcess() if callback: + if self.__outdatedProc is not None: + self.__outdatedProc.kill() # end the process forcefully + self.__outdatedProc = None + + proc = EricProcess(timeout=30000) self.__outdatedProc = proc proc.finished.connect( functools.partial(self.__outdatedFinished, callback, proc) @@ -934,6 +949,7 @@ proc.start(interpreter, args) return None + proc = QProcess() proc.start(interpreter, args) if proc.waitForStarted(15000) and proc.waitForFinished(30000): packages = self.__extractOutdatedPackages(proc) @@ -989,7 +1005,11 @@ """ packages = ( self.__extractOutdatedPackages(proc) - if exitStatus == QProcess.ExitStatus.NormalExit and exitCode == 0 + if ( + not proc.timedOut() + and exitStatus == QProcess.ExitStatus.NormalExit + and exitCode == 0 + ) else {} ) callback(packages)
--- a/src/eric7/PipInterface/PipPackagesWidget.py Fri Sep 06 14:56:18 2024 +0200 +++ b/src/eric7/PipInterface/PipPackagesWidget.py Sun Sep 15 16:56:58 2024 +0200 @@ -19,7 +19,6 @@ from PyQt6.QtNetwork import QNetworkReply, QNetworkRequest from PyQt6.QtWidgets import ( QAbstractItemView, - QApplication, QDialog, QHeaderView, QMenu, @@ -470,7 +469,6 @@ self.packagesList.resizeColumnToContents( PipPackagesWidget.InstalledVersionColumn ) - QApplication.processEvents() # 2. update with vulnerability information if self.vulnerabilityCheckBox.isChecked(): @@ -479,7 +477,6 @@ PipPackagesWidget.VulnerabilityColumn ) self.statusLabel.setText(self.tr("Getting outdated packages...")) - QApplication.processEvents() # 3. update with update information self.__pip.getOutdatedPackages( @@ -514,13 +511,14 @@ self.packagesList.resizeColumnToContents( PipPackagesWidget.AvailableVersionColumn ) - self.statusLabel.hide() self.__updateActionButtons() self.__updateSearchActionButtons() self.__updateSearchButton() self.__updateSearchMoreButton(False) + self.statusLabel.hide() + @pyqtSlot(str) def on_environmentsComboBox_currentTextChanged(self, name): """
--- a/src/eric7/PipInterface/PipPackagesWindow.py Fri Sep 06 14:56:18 2024 +0200 +++ b/src/eric7/PipInterface/PipPackagesWindow.py Sun Sep 15 16:56:58 2024 +0200 @@ -57,3 +57,12 @@ self.__buttonBox.accepted.connect(self.close) self.__buttonBox.rejected.connect(self.close) + + def closeEvent(self, evt): + """ + Protected method handling a close event. + + @param evt reference to the close event object + @type QCloseEvent + """ + self.__pip.shutdown()
--- a/src/eric7/UI/UserInterface.py Fri Sep 06 14:56:18 2024 +0200 +++ b/src/eric7/UI/UserInterface.py Sun Sep 15 16:56:58 2024 +0200 @@ -8474,6 +8474,8 @@ if self.microPythonWidget is not None: self.microPythonWidget.shutdown() + self.pipInterface.shutdown() + self.pluginManager.doShutdown() if self.SAServer is not None: