pip Interface eric7

Mon, 04 Mar 2024 11:26:52 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 04 Mar 2024 11:26:52 +0100
branch
eric7
changeset 10620
699b5ceb39aa
parent 10619
bd15b5b625cb
child 10621
f5631f40c4d9

pip Interface
- Added the capability to install packages given in the `project.dependencies` section of a `pyproject.toml` file.
(see issue 546)

docs/changelog.md file | annotate | diff | comparison | revisions
src/eric7/PipInterface/Pip.py file | annotate | diff | comparison | revisions
src/eric7/PipInterface/PipFileSelectionDialog.py file | annotate | diff | comparison | revisions
src/eric7/PipInterface/PipPackagesWidget.py file | annotate | diff | comparison | revisions
--- a/docs/changelog.md	Sun Mar 03 16:31:14 2024 +0100
+++ b/docs/changelog.md	Mon Mar 04 11:26:52 2024 +0100
@@ -2,6 +2,9 @@
 
 ### Version 24.4
 - bug fixes
+- pip Interface
+    - Added the capability to install packages given in the `project.dependencies`
+      section of a `pyproject.toml` file.
 - Project
     - Added an action to the `Other Tools` menu to clear the byte code caches
       of the project.
--- a/src/eric7/PipInterface/Pip.py	Sun Mar 03 16:31:14 2024 +0100
+++ b/src/eric7/PipInterface/Pip.py	Mon Mar 04 11:26:52 2024 +0100
@@ -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 (tomlkit.exceptions.ParseError, OSError) 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 (tomlkit.exceptions.ParseError, OSError) 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)
+                            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.
--- a/src/eric7/PipInterface/PipFileSelectionDialog.py	Sun Mar 03 16:31:14 2024 +0100
+++ b/src/eric7/PipInterface/PipFileSelectionDialog.py	Mon Mar 04 11:26:52 2024 +0100
@@ -50,6 +50,16 @@
                 )
             )
             self.filePicker.setFilters(self.tr("Text Files (*.txt);;All Files (*)"))
+        elif mode == "pyproject":
+            self.fileLabel.setText(self.tr("Enter 'pyproject.toml' file:"))
+            self.filePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
+            self.filePicker.setToolTip(
+                self.tr(
+                    "Press to select the 'pyproject.toml' file through a file"
+                    " selection dialog."
+                )
+            )
+            self.filePicker.setFilters(self.tr("TOML Files (*.toml);;All Files (*)"))
         elif mode == "package":
             self.fileLabel.setText(self.tr("Enter package file:"))
             self.filePicker.setMode(EricPathPickerModes.OPEN_FILE_MODE)
--- a/src/eric7/PipInterface/PipPackagesWidget.py	Sun Mar 03 16:31:14 2024 +0100
+++ b/src/eric7/PipInterface/PipPackagesWidget.py	Mon Mar 04 11:26:52 2024 +0100
@@ -1289,6 +1289,15 @@
             self.tr("Generate Requirements..."), self.__generateRequirements
         )
         self.__requirementsSubenu.addSeparator()
+        self.__installPyprojectAct = self.__requirementsSubenu.addAction(
+            self.tr("Install from 'pyproject.toml'"),
+            self.__installPyprojectDependencies,
+        )
+        self.__uninstallPyprojectAct = self.__requirementsSubenu.addAction(
+            self.tr("Uninstall from 'pyproject.toml'"),
+            self.__uninstallPyprojectDependencies,
+        )
+        self.__requirementsSubenu.addSeparator()
         self.__generateConstraintsAct = self.__requirementsSubenu.addAction(
             self.tr("Generate Constraints..."), self.__generateConstraints
         )
@@ -1518,6 +1527,26 @@
             self.__freezeDialog.start(venvName)
 
     @pyqtSlot()
+    def __installPyprojectDependencies(self):
+        """
+        Private slot to install packages as given in a 'pyproject.toml' file.
+        """
+        venvName = self.environmentsComboBox.currentText()
+        if venvName:
+            self.__pip.installPyprojectDependencies(venvName)
+            self.on_refreshButton_clicked()
+
+    @pyqtSlot()
+    def __uninstallPyprojectDependencies(self):
+        """
+        Private slot to uninstall packages as given in a 'pyproject.toml' file.
+        """
+        venvName = self.environmentsComboBox.currentText()
+        if venvName:
+            self.__pip.uninstallPyprojectDependencies(venvName)
+            self.on_refreshButton_clicked()
+
+    @pyqtSlot()
     def __editUserConfiguration(self):
         """
         Private slot to edit the user configuration.

eric ide

mercurial