Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python virtual environment creation fixes and refactor #107

Merged
merged 13 commits into from
Aug 9, 2023
Merged
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,8 @@ Alligator/Alligator_REMOTE_9848.csproj

# VSCode User File #
####################
.vscode/
.vscode/

# User defined files #
######################
build.ps1
99 changes: 99 additions & 0 deletions Python_Engine/Compute/BasePythonEnvironment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2023, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.oM.Base.Attributes;
using BH.oM.Python;
using BH.oM.Python.Enums;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;

namespace BH.Engine.Python
{
public static partial class Compute
{
[Description("Retrieve or reinstall the base Python Environment for BHoM workflows.")]
[Input("reload", "Reload the base Python environment rather than recreating it, if it already exists.")]
[Output("env", "The base Python Environment for all BHoM workflows.")]
public static PythonEnvironment BasePythonEnvironment(
bool reload = true
)
{
// determine whether the base environment already exists
string targetExecutable = Path.Combine(Query.DirectoryBaseEnvironment(), "python.exe");
bool exists = Directory.Exists(Query.DirectoryBaseEnvironment()) && File.Exists(targetExecutable);

if (exists && reload)
return new PythonEnvironment() { Name = Query.ToolkitName(), Executable = targetExecutable };

if (exists && !reload)
// remove all existing environments and kernels
RemoveEverything();

// download the target Python version and convert into a "full" python installation bypassing admin rights
string executable = PythonVersion.v3_10_5.DownloadPython(Query.ToolkitName());
string pipInstaller = DownloadGetPip(Path.GetDirectoryName(executable));
string baseEnvironmentDirectory = Path.GetDirectoryName(executable);

// install pip into the python installation
Process process = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = Modify.AddQuotesIfRequired(executable),
Arguments = Modify.AddQuotesIfRequired(pipInstaller) + " --no-warn-script-location",
RedirectStandardError=true,
UseShellExecute=false,
}
};
using (Process p = Process.Start(process.StartInfo))
{
p.WaitForExit();
if (p.ExitCode != 0)
BH.Engine.Base.Compute.RecordError($"Error installing pip.\n{p.StandardError.ReadToEnd()}");
File.Delete(pipInstaller);
}

// delete files with the suffix ._pth from installedDirectory
List<string> pthFiles = Directory.GetFiles(baseEnvironmentDirectory, "*.*", SearchOption.TopDirectoryOnly).Where(s => s.EndsWith("._pth")).ToList();
foreach (string pthFile in pthFiles)
{
File.Delete(pthFile);
}

// move files with the suffix .dll and .pyd from installedDirectory into a DLLs directory
string libDirectory = Directory.CreateDirectory(Path.Combine(baseEnvironmentDirectory, "DLLs")).FullName;
List<string> libFiles = Directory.GetFiles(baseEnvironmentDirectory, "*.*", SearchOption.TopDirectoryOnly).Where(s => (s.EndsWith(".dll") || s.EndsWith(".pyd")) && !Path.GetFileName(s).Contains("python") && !Path.GetFileName(s).Contains("vcruntime")).ToList();
foreach (string libFile in libFiles)
{
File.Move(libFile, Path.Combine(libDirectory, Path.GetFileName(libFile)));
}

// install essential packages into base environment
InstallPackages(executable, new List<string>() { "virtualenv", "jupyterlab", "black", "pylint" });

return new PythonEnvironment() { Name = Query.ToolkitName(), Executable = executable };
}
}
}
117 changes: 117 additions & 0 deletions Python_Engine/Compute/Download.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2023, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.oM.Base.Attributes;
using BH.oM.Python.Enums;
using System;
using System.ComponentModel;
using System.IO;
using System.Xml.Linq;

namespace BH.Engine.Python
{
public static partial class Compute
{
[Description("Download a file from a Url.")]
[Input("fileUrl", "The file to download.")]
[Input("targetDirectory", "The destination directory to download the file to.")]
[Input("filename", "An optional input to rename the downloaded file.")]
[Input("overwrite", "An optional input to overwrite the file if it already exists, otherwise if it does then return the existing file.")]
[Output("filePath", "The path to the downloaded file.")]
public static string DownloadFile(
string fileUrl,
string targetDirectory,
string filename = null,
bool overwrite = true
)
{
if (string.IsNullOrWhiteSpace(fileUrl))
{
BH.Engine.Base.Compute.RecordError($"The fileUrl cannot be null or empty.");
return null;
}


if (string.IsNullOrWhiteSpace(targetDirectory))
{
BH.Engine.Base.Compute.RecordError($"The targetDirectory cannot be null or empty.");
return null;
}


if (!Directory.Exists(targetDirectory))
{
BH.Engine.Base.Compute.RecordError($"The targetDirectory \"{targetDirectory}\" does not exist.");
return null;
}


if (string.IsNullOrWhiteSpace(filename))
filename = Path.GetFileName(fileUrl);

string filePath = Path.Combine(targetDirectory, filename);

if (File.Exists(filePath))
{
if (!overwrite)
return filePath;
File.Delete(filePath);
}

using (var client = new System.Net.WebClient())
{
client.DownloadFile(fileUrl, filePath);
}

return filePath;
}

// TODO - THIS METHOD HAS CHANGED BUT IS STILL USED, SO NEEDS DEPRECATING
[Description("Download the target version of Python.")]
[Input("version", "A Python version.")]
[Output("executablePath", "The path of the executable for the downloaded Python.")]
[PreviousVersion("6.3", "BH.Engine.Python.Compute.DownloadPython(BH.oM.Python.Enums.PythonVersion)")]
public static string DownloadPython(this PythonVersion version, string name = null)
{
string url = version.EmbeddableURL();
if (string.IsNullOrEmpty(name))
name = Path.GetFileNameWithoutExtension(url);
string targetExecutable = Path.Combine(Query.DirectoryEnvironments(), name, "python.exe");

if (File.Exists(targetExecutable))
return targetExecutable;

string zipfile = DownloadFile(url, Query.DirectoryEnvironments());
UnzipFile(zipfile, Query.DirectoryEnvironments(), name, true);

return targetExecutable;
}

[Description("Download the pip installer")]
[Input("targetDirectory", "The directory into which get-pip.py will be downloaded.")]
[Output("getpipPath", "The path of the file used to install pip into an embedded Python environment.")]
public static string DownloadGetPip(string targetDirectory)
{
return DownloadFile("https://bootstrap.pypa.io/get-pip.py", targetDirectory);
}
}
}
65 changes: 0 additions & 65 deletions Python_Engine/Compute/DownloadPython.cs

This file was deleted.

1 change: 1 addition & 0 deletions Python_Engine/Compute/InstallBasePythonEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ namespace BH.Engine.Python
{
public static partial class Compute
{
// TODO - REMOVE THIS METHOD, NO LONGER REQUIRED
[Description("Install the base Python Environment for BHoM workflows.")]
[Input("run", "Run the installation process for the BHoM Python Environment.")]
[Output("env", "The base Python Environment for BHoM workflows.")]
Expand Down
Loading