--- a/src/eric7/PipInterface/Pip.py Sun Mar 03 10:39:56 2024 +0100 +++ b/src/eric7/PipInterface/Pip.py Tue Apr 02 10:00:22 2024 +0200 @@ -11,8 +11,11 @@ import functools import json import os +import re import sys +import tomlkit + from PyQt6.QtCore import QCoreApplication, QObject, QProcess, QThread, QUrl, pyqtSlot from PyQt6.QtNetwork import ( QNetworkAccessManager, @@ -545,6 +548,61 @@ if res: dia.exec() + def installPyprojectDependencies(self, venvName): + """ + Public method to install the dependencies listed in a pyproject.toml file. + + @param venvName name of the virtual environment to be used + @type str + """ + from .PipFileSelectionDialog import PipFileSelectionDialog + + dlg = PipFileSelectionDialog(self, "pyproject") + if dlg.exec() == QDialog.DialogCode.Accepted: + pyproject, user = dlg.getData() + if pyproject and os.path.exists(pyproject): + try: + with open(pyproject, "r", encoding="utf-8") as f: + data = tomlkit.load(f) + dependencies = data.get("project", {}).get("dependencies", []) + if not dependencies: + EricMessageBox.warning( + None, + self.tr("Install 'pyproject' Dependencies"), + self.tr( + "The selected 'pyproject.toml' file does not contain" + " a 'project.dependencies' section. Aborting..." + ), + ) + return + except (OSError, tomlkit.exceptions.ParseError) as err: + EricMessageBox.warning( + None, + self.tr("Install 'pyproject' Dependencies"), + self.tr( + "<p>The selected 'pyproject.toml' file could not be read." + "</p><p>Reason: {0}</p>" + ).format(str(err)), + ) + return + + interpreter = self.getVirtualenvInterpreter(venvName) + if not interpreter: + return + + if Preferences.getPip("PipSearchIndex"): + indexUrl = Preferences.getPip("PipSearchIndex") + "/simple" + args = ["-m", "pip", "install", "--index-url", indexUrl] + else: + args = ["-m", "pip", "install"] + if user: + args.append("--user") + args += dependencies + dia = PipDialog(self.tr("Install Packages from 'pyproject.toml'")) + res = dia.startProcess(interpreter, args) + if res: + dia.exec() + def uninstallPackages(self, packages, venvName): """ Public method to uninstall the given list of packages. @@ -606,12 +664,85 @@ if not interpreter: return - args = ["-m", "pip", "uninstall", "--requirement", requirements] + args = [ + "-m", + "pip", + "uninstall", + "--yes", + "--requirement", + requirements, + ] dia = PipDialog(self.tr("Uninstall Packages from Requirements")) res = dia.startProcess(interpreter, args) if res: dia.exec() + def uninstallPyprojectDependencies(self, venvName): + """ + Public method to uninstall the dependencies listed in a pyproject.toml file. + + @param venvName name of the virtual environment to be used + @type str + """ + from .PipFileSelectionDialog import PipFileSelectionDialog + + if venvName: + dlg = PipFileSelectionDialog(self, "pyproject", install=False) + if dlg.exec() == QDialog.DialogCode.Accepted: + pyproject, _user = dlg.getData() + if pyproject and os.path.exists(pyproject): + try: + with open(pyproject, "r", encoding="utf-8") as f: + data = tomlkit.load(f) + dependencies = data.get("project", {}).get("dependencies", []) + if not dependencies: + EricMessageBox.warning( + None, + self.tr("Uninstall 'pyproject' Dependencies"), + self.tr( + "The selected 'pyproject.toml' file does not" + " contain a 'project.dependencies' section." + " Aborting..." + ), + ) + return + except (OSError, tomlkit.exceptions.ParseError) as err: + EricMessageBox.warning( + None, + self.tr("Uninstall 'pyproject' Dependencies"), + self.tr( + "<p>The selected 'pyproject.toml' file could not be" + " read. </p><p>Reason: {0}</p>" + ).format(str(err)), + ) + return + + # Do not uninstall pip. + pipre = re.compile(r"^pip\s*(~=|==|!=|<=|>=|<|>|===)") + for dependency in dependencies: + if pipre.search(dependency): + dependencies.remove(dependency) # noqa: M569 + break + + dlg = DeleteFilesConfirmationDialog( + self.parent(), + self.tr("Uninstall Packages"), + self.tr("Do you really want to uninstall these packages?"), + dependencies, + ) + if dlg.exec() == QDialog.DialogCode.Accepted: + interpreter = self.getVirtualenvInterpreter(venvName) + if not interpreter: + return + + args = ["-m", "pip", "uninstall", "--yes"] + dependencies + dia = PipDialog( + self.tr("Uninstall Packages from 'pyproject.toml'") + ) + res = dia.startProcess(interpreter, args) + if res: + dia.exec() + def getIndexUrl(self): """ Public method to get the index URL for PyPI.