--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PipxInterface/Pipx.py Mon Jun 24 17:13:07 2024 +0200 @@ -0,0 +1,180 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2024 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the pipx GUI logic. +""" + +import contextlib +import json +import os +import sys +import sysconfig + +from PyQt6.QtCore import QObject, QProcess + +from eric7 import Preferences +from eric7.SystemUtilities import OSUtilities + + +class Pipx(QObject): + """ + Class implementing the pip GUI logic. + """ + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the user interface object + @type QObject + """ + super().__init__(parent) + + self.__ui = parent + + ############################################################################ + ## Utility methods + ############################################################################ + + def getPipxVersion(self): + """ + Public method to get the version of the installed pipx package. + + @return string containing the pipx version number + @rtype str + """ + from pipx.version import version + + return version + + def getPipxVersionTuple(self): + """ + Public method to get the version tuple of the installed pipx package. + + @return tuple containing the elements of the pipx version number + @rtype tuple of (int, int, int) + """ + from pipx.version import version_tuple + + return version_tuple + + def getPipxPaths(self): + """ + Public method to get the paths used by pipx. + + @return dictionary containing the various pipx paths. The keys are + 'venvsPath', 'appsPath' and 'manPath'. + @rtype dict[str, Path] + """ + from pipx.paths import ctx + + return { + "venvsPath": ctx.venvs, + "appsPath": ctx.bin_dir, + "manPath": ctx.man_dir, + } + + def getPipxStrPaths(self): + """ + Public method to get the paths used by pipx. + + @return dictionary containing the various pipx paths. The keys are + 'venvsPath', 'appsPath' and 'manPath'. + @rtype dict[str, str] + """ + from pipx.paths import ctx + + return { + "venvsPath": str(ctx.venvs), + "appsPath": str(ctx.bin_dir), + "manPath": str(ctx.man_dir), + } + + def __getPipxExecutable(self): + """ + Private method to get the path name of the pipx executable. + + @return path name of the pipx executable + @rtype str + """ + binDir = sysconfig.get_path("scripts") + pipx = os.path.join(binDir, "pipx") + if OSUtilities.isWindowsPlatform: + pipx += ".exe" + + return pipx + + def runProcess(self, args): + """ + Public method to execute pipx with the given arguments. + + @param args list of command line arguments for pipx + @type list of str + @return tuple containing a flag indicating success and the output + of the process + @rtype tuple of (bool, str) + """ + ioEncoding = Preferences.getSystem("IOEncoding") + + process = QProcess() + process.start(sys.executable, ["-m", "pipx"] + args) + procStarted = process.waitForStarted() + if procStarted: + finished = process.waitForFinished(30000) + if finished: + if process.exitCode() == 0: + output = str(process.readAllStandardOutput(), ioEncoding, "replace") + return True, output + else: + error = str(process.readAllStandardError(), ioEncoding, "replace") + msg = self.tr("<p>Message:{0}</p>").format(error) if error else "" + return ( + False, + self.tr("<p>pipx exited with an error ({0}).</p>{1}").format( + process.exitCode(), msg + ), + ) + else: + process.terminate() + process.waitForFinished(2000) + process.kill() + process.waitForFinished(3000) + return False, self.tr("pipx did not finish within 30 seconds.") + + return False, self.tr("pipx could not be started.") + + ############################################################################ + ## Command methods + ############################################################################ + + def getInstalledPackages(self): + """ + Public method to get the installed packages. + + @return list of dictionaries containing the installed packages and apps + @rtype list of dict[str, str | list] + """ + packages = [] + + ok, output = self.runProcess(["list", "--json"]) + if ok: + if output: + with contextlib.suppress(json.JSONDecodeError): + data = json.loads(output) + for venvName in data["venvs"]: + metadata = data["venvs"][venvName]["metadata"] + package = { + "name": venvName, + "version": metadata["main_package"]["package_version"], + "apps": [], + "python": metadata["python_version"], + } + for appPath in metadata["main_package"]["app_paths"]: + path = appPath["__Path__"] + package["apps"].append((os.path.basename(path), path)) + packages.append(package) + + return packages