PipxInterface/Pipx.py

changeset 111
8ace016a3eee
parent 106
dcc8d6b448fa
child 116
0f49bfab0768
--- a/PipxInterface/Pipx.py	Tue Dec 10 17:53:34 2024 +0100
+++ b/PipxInterface/Pipx.py	Fri Dec 13 15:40:08 2024 +0100
@@ -14,11 +14,12 @@
 import pathlib
 import sysconfig
 
+from packaging.specifiers import InvalidSpecifier, SpecifierSet
 from PyQt6.QtCore import QObject, QProcess, pyqtSignal
 
 from eric7 import Preferences
 from eric7.EricWidgets import EricMessageBox
-from eric7.SystemUtilities import OSUtilities
+from eric7.SystemUtilities import OSUtilities, PythonUtilities
 
 try:
     from eric7.EricCore.EricProcess import EricProcess
@@ -818,3 +819,148 @@
                 self.tr("Upgrade Dependencies"),
                 self.tr("""All dependencies are already up-to-date."""),
             )
+
+    def __getPackageInterpreter(self, package):
+        """
+        Private method to determine the executable path of the python interpreter
+        of a package.
+
+        @param package name of the package
+        @type str
+        @return Python interpreter path
+        @rtype str
+        """
+        from pipx.paths import ctx  # noqa: I102
+        from pipx.venv import Venv  # noqa: I102
+
+        packagePath = ctx.venvs / package
+        _venv = Venv(packagePath)
+        return str(_venv.python_path)
+##
+    ##def __addDependency(self, dependency, parent):
+        ##"""
+        ##Private method to add a dependency branch to a given parent.
+##
+        ##@param dependency dependency to be added
+        ##@type dict
+        ##@param parent reference to the parent item
+        ##@type QTreeWidget or QTreeWidgetItem
+        ##"""
+        ##itm = QTreeWidgetItem(
+            ##parent,
+            ##[
+                ##dependency["package_name"],
+                ##dependency["installed_version"],
+                ##dependency["required_version"],
+            ##],
+        ##)
+        ##itm.setExpanded(True)
+##
+        ##if dependency["installed_version"] == "?":
+            ##itm.setText(PipPackagesWidget.DepInstalledVersionColumn, self.tr("unknown"))
+##
+        ##if dependency["required_version"].lower() not in ("any", "?"):
+            ##spec = (
+                ##"=={0}".format(dependency["required_version"])
+                ##if dependency["required_version"][0] in "0123456789"
+                ##else dependency["required_version"]
+            ##)
+            ##try:
+                ##specifierSet = SpecifierSet(specifiers=spec)
+                ##if not specifierSet.contains(dependency["installed_version"]):
+                    ##itm.setIcon(
+                        ##PipPackagesWidget.DepRequiredVersionColumn,
+                        ##EricPixmapCache.getIcon("warning"),
+                    ##)
+            ##except InvalidSpecifier:
+                ##itm.setText(
+                    ##PipPackagesWidget.DepRequiredVersionColumn,
+                    ##dependency["required_version"],
+                ##)
+##
+        ##elif dependency["required_version"].lower() == "any":
+            ##itm.setText(PipPackagesWidget.DepRequiredVersionColumn, self.tr("any"))
+##
+        ##elif dependency["required_version"] == "?":
+            ##itm.setText(PipPackagesWidget.DepRequiredVersionColumn, self.tr("unknown"))
+##
+        ### recursively add sub-dependencies
+        ##for dep in dependency["dependencies"]:
+            ##self.__addDependency(dep, itm)
+
+    def __getBrokenDependencies(self, dependencies):
+        brokenDependecies = []
+
+        for dependency in dependencies:
+            if dependency["required_version"].lower() not in ("any", "?"):
+                spec = (
+                    "=={0}".format(dependency["required_version"])
+                    if dependency["required_version"][0] in "0123456789"
+                    else dependency["required_version"]
+                )
+                with contextlib.suppress(InvalidSpecifier):
+                    specifierSet = SpecifierSet(specifiers=spec)
+                    if not specifierSet.contains(dependency["installed_version"]):
+                        brokenDependecies.append(f"{dependency["package_name"]}{spec}")
+
+            # recursively add sub-dependencies
+            brokenDependecies.extend(
+                self.__getBrokenDependencies(dependency["dependencies"])
+            )
+
+        return brokenDependecies
+
+    def repairBrokenDependencies(self, package):
+        """
+        Public method to get repair broken or unmet package dependencies.
+
+        @param package name of the package
+        @type str
+        """
+        dependencies = []
+
+        interpreter = self.__getPackageInterpreter(package=package)
+        if interpreter:
+            args = ["-m", "pipdeptree", "--python", interpreter, "--json-tree"]
+
+            proc = QProcess()
+            proc.start(PythonUtilities.getPythonExecutable(), args)
+            if proc.waitForStarted(15000) and proc.waitForFinished(30000):
+                output = str(
+                    proc.readAllStandardOutput(),
+                    Preferences.getSystem("IOEncoding"),
+                    "replace",
+                ).strip()
+                with contextlib.suppress(json.JSONDecodeError):
+                    dependencies = json.loads(output)
+
+            brokenDependecies = self.__getBrokenDependencies(dependencies)
+            if brokenDependecies:
+                args = [
+                    "runpip",
+                    package,
+                    "install",
+                    "--prefer-binary",
+                ] + brokenDependecies
+
+                dia = PipxExecDialog(
+                    self.tr("Repair Broken Dependencies"), parent=self.__ui
+                )
+                res = dia.startProcess(self.__getPipxExecutable(), args)
+                if res:
+                    dia.exec()
+            else:
+                EricMessageBox.information(
+                    self.__ui,
+                    self.tr("Repair Broken Dependencies"),
+                    self.tr("There are no broken dependencies."),
+                )
+        else:
+            EricMessageBox.critical(
+                self.__ui,
+                self.tr("Repair Broken Dependencies"),
+                self.tr(
+                    "<p>The interpreter for package <b>{0}</b> could not be determined."
+                    " Aborting...</p>"
+                ).format(package),
+            )

eric ide

mercurial