From d741d5d605f166e3fc85a5dabb00804ed478ac74 Mon Sep 17 00:00:00 2001 From: Rob Purser Date: Sat, 12 Oct 2024 07:04:02 -0400 Subject: [PATCH] A set of mlboost.python functions to work with PIP3 --- resources/project/Project.xml | 755 ++++++++++++----------- toolbox/+mlboost/+python/ensurepip.m | 49 ++ toolbox/+mlboost/+python/getPipPath.m | 36 ++ toolbox/+mlboost/+python/getPythonPath.m | 14 + toolbox/+mlboost/+python/pipCommand.m | 38 ++ toolbox/+mlboost/+python/pipInstall.m | 71 +++ toolbox/+mlboost/+python/pipList.m | 17 + toolbox/+mlboost/+python/pipShow.m | 43 ++ 8 files changed, 676 insertions(+), 347 deletions(-) create mode 100644 toolbox/+mlboost/+python/ensurepip.m create mode 100644 toolbox/+mlboost/+python/getPipPath.m create mode 100644 toolbox/+mlboost/+python/getPythonPath.m create mode 100644 toolbox/+mlboost/+python/pipCommand.m create mode 100644 toolbox/+mlboost/+python/pipInstall.m create mode 100644 toolbox/+mlboost/+python/pipList.m create mode 100644 toolbox/+mlboost/+python/pipShow.m diff --git a/resources/project/Project.xml b/resources/project/Project.xml index fca9c2e..d9fb3cb 100644 --- a/resources/project/Project.xml +++ b/resources/project/Project.xml @@ -1,366 +1,427 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - + + - - - - - + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/toolbox/+mlboost/+python/ensurepip.m b/toolbox/+mlboost/+python/ensurepip.m new file mode 100644 index 0000000..ec394db --- /dev/null +++ b/toolbox/+mlboost/+python/ensurepip.m @@ -0,0 +1,49 @@ +function ensurepip(option) +%ENSUREPIP Downloads and runs get-pip.py to ensure pip is installed +% +% ensurepip() downloads and runs get-pip.py to install pip. +% +% ensurepip(Name,Value) specifies additional options using one or more +% name-value pair arguments: +% +% 'ShowSuccessMessage' Logical value indicating whether to display a +% success message after installation. Default is false. +% +% Example: +% ensurepip('ShowSuccessMessage', true) +% +% See also mlboost.python.getPythonExecutable, mlboost.python.getPipPath + + arguments + option.ShowSuccessMessage (1,1) logical = false; + end + + % Create a temporary directory + tempDir = tempname; + mkdir(tempDir); + + % Set up cleanup object to + cleanupObj = onCleanup(@() rmdir(tempDir, 's')); + + % Download get-pip.py + url = "https://bootstrap.pypa.io/get-pip.py"; + outputFile = fullfile(tempDir, "get-pip.py"); + websave(outputFile, url); + + % Run get-pip.py + pythonExe = mlboost.python.getPythonExecutable(); + command = sprintf('"%s" "%s"', pythonExe, outputFile); + + [status, output] = system(command); + + if status == 0 + if option.ShowSuccessMessage + successMessage = extract(string(output),"Successfully installed" + wildcardPattern(3,inf) + whitespaceBoundary("start")); + disp(successMessage); + end + else + error("mlboost:pipinstallfailed","Failed to install pip. Error: %s", output); + end + + % Cleanup will be automatically performed when the function exits +end \ No newline at end of file diff --git a/toolbox/+mlboost/+python/getPipPath.m b/toolbox/+mlboost/+python/getPipPath.m new file mode 100644 index 0000000..5b268d8 --- /dev/null +++ b/toolbox/+mlboost/+python/getPipPath.m @@ -0,0 +1,36 @@ +function pipPath = getPipPath() + %GETPIPPATH Get the location of the pip3 executable + % pipPath = getPipPath() returns the full path to the pip3 executable + % using the Python environment information obtained from pyenv. + % + % This function constructs the path to the pip3 executable, verifies its + % existence, and returns the full path. If the pip executable is not found, + % it throws an error. + % + % Returns: + % getPipPath (string): Full path to the pip3 executable + % + % Throws: + % mlboost:python:PIPNotFound: If the pip executable cannot be located + % + % See Also: + % mlboost.python.getPythonPath, mlboost.python.pipCommand, mlboost.python.ensurepip + + % Get the Python environment + pyEnv = pyenv; + + % Get the Python executable path + pythonHomePath = pyEnv.Home; + + % Construct the pip3 executable path + pythonScriptsPath = fullfile(pythonHomePath, "Scripts"); % Construct the path to the Scripts directory where pip is typically located + pipPath = dir(fullfile(pythonScriptsPath,"pip3.*")); % Search for pip3 executable files in the Scripts directory + pipPath = string({pipPath.name}); % Convert the found file names to a string array + pipPath = pipPath(matches(pipPath,"pip3." + lettersPattern + textBoundary("end"))); % Filter the array to keep only valid pip3 executable names + pipPath = fullfile(pythonScriptsPath,pipPath); % Construct the full path to the pip3 executable + + % Verify that the pip executable exists + if ~exist(pipPath, 'file') + error('mlboost:python:PIPNotFound', 'Unable to locate pip executable.'); + end +end \ No newline at end of file diff --git a/toolbox/+mlboost/+python/getPythonPath.m b/toolbox/+mlboost/+python/getPythonPath.m new file mode 100644 index 0000000..3a6f2a7 --- /dev/null +++ b/toolbox/+mlboost/+python/getPythonPath.m @@ -0,0 +1,14 @@ +%GETPYTHONPATH Get the Python executable path +% pythonPath = getPythonPath() returns the full path to the Python +% executable currently used by MATLAB. +% +% This function uses the pyenv command to retrieve information about the +% Python environment and extracts the path to the Python executable. +% +% Returns: +% pythonPath - A string containing the full path to the Python executable +function pythonPath = getPythonPath() + % Get the Python executable path using pyenv + pyEnv = pyenv; + pythonPath = pyEnv.Executable; +end diff --git a/toolbox/+mlboost/+python/pipCommand.m b/toolbox/+mlboost/+python/pipCommand.m new file mode 100644 index 0000000..b59bee7 --- /dev/null +++ b/toolbox/+mlboost/+python/pipCommand.m @@ -0,0 +1,38 @@ +function commandOutput = pipCommand(pipArguments) +% Execute a pip command +% This function executes a pip command using the specified pip executable path. +% +% Syntax: +% outputResult = pipCommand(pipArguments) +% +% Inputs: +% pipArguments - A string containing the command for pip and its arguments +% +% Outputs: +% outputResult - The output of the executed pip command +% +% Throws: +% An error if no arguments are provided or if the pip command fails +% +% Example: +% outputResult = pipCommand('install cdsapi') +% +% See also: +% mlboost.python.getPipPath + + arguments + pipArguments (1,1) string + end + + % Combine the PIP command with the provided arguments + fullCommand = sprintf('"%s" %s', mlboost.python.getPipPath(), pipArguments); + + % Execute the command using system + [exitStatus, commandOutput] = system(fullCommand); + commandOutput = string(commandOutput); + + % Check if the command was executed successfully + if exitStatus ~= 0 + error("mlboost:pipCommandFailed","pip3 command ""%s"" failed: ""%s"" (exit code %d)", pipArguments, strtrim(commandOutput), exitStatus); + end +end diff --git a/toolbox/+mlboost/+python/pipInstall.m b/toolbox/+mlboost/+python/pipInstall.m new file mode 100644 index 0000000..b8b1f48 --- /dev/null +++ b/toolbox/+mlboost/+python/pipInstall.m @@ -0,0 +1,71 @@ +function installResult = pipInstall(packageName, option) + %PIPINSTALL Install a Python package using pip + % INSTALLRESULT = mlboost.python.pipInstall(PACKAGENAME) installs the + % specified Python package using pip and returns the installation result. + % + % INSTALLRESULT = mlboost.python.pipInstall(PACKAGENAME, 'Name', Value) + % specifies additional installation options using one or more name-value + % pair arguments. + % + % Input arguments: + % PACKAGENAME - Name of the Python package to install (string) + % + % Name-value pair arguments: + % 'version' - Specific version of the package to install (string) + % 'upgrade' - Whether to upgrade the package if already installed + % (logical) + % + % Output: + % INSTALLRESULT - Installation result returned by pip (optional) + % + % Example: + % pipInstall('numpy', 'version', '1.21.0', 'upgrade', true) + % + % See also mlboost.python.pipCommand + + arguments + % Name of the Python package to install + packageName (1,1) string + % Specific version of the package to install (optional) + option.version (1,1) string = ""; + % Whether to upgrade the package if already installed (optional) + option.upgrade (1,1) logical = false + end + + % Build the pip command string based on input arguments Start with + % "install" followed by the package name If a version is specified, + % append it to the command If upgrade option is true, add the + % "--upgrade" flag + command = sprintf("install %s",packageName); + if option.version ~= "" + command = command + sprintf("==%s", option.version); + end + if option.upgrade + command = command + " --upgrade"; + end + + % Execute the pip command using the mlboost.python.pipCommand + % function and store the output in cmdout + cmdout = mlboost.python.pipCommand(command); + + % If an output argument is requested, assign the command output to + % installResult + if nargout > 0 + installResult = cmdout; + end + + % Construct the pip install command + command = sprintf("install %s",packageName); + if option.version ~= "" + command = command + sprintf("==%s", option.version); + end + if option.upgrade + command = command + " --upgrade"; + end + + cmdout = mlboost.python.pipCommand(command); + + if nargout > 0 + installResult = cmdout; + end +end diff --git a/toolbox/+mlboost/+python/pipList.m b/toolbox/+mlboost/+python/pipList.m new file mode 100644 index 0000000..4760487 --- /dev/null +++ b/toolbox/+mlboost/+python/pipList.m @@ -0,0 +1,17 @@ +function pipPackages = pipList() + % Call pip freeze using mlboost.python.boost and parse the results + % Returns a table of installed packages and their versions + + % Call pip freeze + pipFreeze = mlboost.python.pipCommand("freeze"); + + % Split the output into lines, remove empty lines + lines = strtrim(splitlines(pipFreeze)); + lines(lines == "") = []; + + % Split each line into package name and version + parts = split(lines, '=='); + + % Create a table with the package name and version + pipPackages = table(parts(:,1), parts(:,2), 'VariableNames', {'Package', 'Version'}); +end \ No newline at end of file diff --git a/toolbox/+mlboost/+python/pipShow.m b/toolbox/+mlboost/+python/pipShow.m new file mode 100644 index 0000000..969685d --- /dev/null +++ b/toolbox/+mlboost/+python/pipShow.m @@ -0,0 +1,43 @@ +%PIPSHOW Show information about installed Python packages +% RESULT = mlboost.python.pipShow(PACKAGENAME) returns a struct containing information +% about the specified Python package. +% +% Input: +% PACKAGENAME - A string specifying the name of the package to query +% +% Output: +% RESULT - A struct containing package information fields such as +% Name, Version, Summary, Home-page, Author, etc. +% +% This function uses the 'pip show' command to retrieve package +% information and parses the output into a MATLAB struct. +% +% Example: +% info = mlboost.python.pipShow('cdsapi'); +% disp(info.Version); +% +% See also mlboost.python.pipCommand +function result = pipShow(packageName) + + arguments + packageName (1,1) string + end + + % Call pip show on the specified package + pipShowOutput = mlboost.python.pipCommand("show " + packageName); + + % Initialize the struct + result = struct(); + + % Split the output into lines and remove extra whitespace + lines = strsplit(pipShowOutput, '\n'); + lines = strtrim(lines); + lines(lines == "") = []; + + % Parse each line and add to struct + for i = 1:length(lines) + property = strtrim(extractBefore(lines(i),":")); + value = strtrim(extractAfter(lines(i),":")); + result.(matlab.lang.makeValidName(property)) = value; + end +end