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

Ensure availability of base Python environment #74

Merged
126 changes: 111 additions & 15 deletions Python_Engine/Compute/InstallPythonEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,32 +25,128 @@

using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System;
using System.Linq;

namespace BH.Engine.Python
{
public static partial class Compute
{
[Description("Install the default BHoM Python_Toolkit environment.")]
[Input("run", "Set to True to run the PythonEnvironment installer.")]
[Description("Install the BHoM Python environment.")]
[Input("pythonEnvironment", "A BHoM Python environment.")]
[Input("force", "If the environment already exists recreate it rather than re-using it.")]
[Output("pythonEnvironment", "The default BHoM Python_Toolkit environment object.")]
public static PythonEnvironment InstallPythonEnvironment(bool run = false, bool force = false)
[Input("run", "Set to True to run the PythonEnvironment installer.")]
[Output("pythonEnvironment", "A BHoM PythonEnvironment object.")]
public static PythonEnvironment InstallPythonEnvironment(this PythonEnvironment pythonEnvironment, bool force = false, bool run = false)
FraserGreenroyd marked this conversation as resolved.
Show resolved Hide resolved
{
oM.Python.Enums.PythonVersion version = oM.Python.Enums.PythonVersion.v3_9_7;
if (!run)
{
BH.Engine.Base.Compute.RecordNote($"This component will install a Python environment for {pythonEnvironment.Name} if it doesn not exist, or return the eixtsing environment if it does.");
return null;
}

// load existing environment if it matches the requested environment
PythonEnvironment existingEnvironment = Query.LoadPythonEnvironment(pythonEnvironment.Name);
if (existingEnvironment.IsInstalled())
{
if (!force)
{
if (existingEnvironment.Version != pythonEnvironment.Version)
{
BH.Engine.Base.Compute.RecordError($"An environment exists with the given name, but its Python version does not match the given version. To overwrite this existing environment set \"force\" to True.");
return null;
}

// check that the existing environment contains the packages requested
List<PythonPackage> existingPackages = existingEnvironment.Packages;
foreach (PythonPackage pkg in pythonEnvironment.Packages)
{
if (!existingPackages.PackageInList(pkg))
{
BH.Engine.Base.Compute.RecordError($"An environment exists with the given name, but it doesn't contain {pkg.GetString()}. To overwrite this existing environment set \"force\" to True.");
return null;
}
}
return existingEnvironment;
}
else
{
existingEnvironment.RemoveEnvironment(true);
}
}

// create the new environment directory
string environmentDirectory = pythonEnvironment.EnvironmentDirectory();
System.IO.Directory.CreateDirectory(environmentDirectory);

// download the target version of Python
string embeddablePythonZip = Compute.DownloadFile(pythonEnvironment.Version.EmbeddableURL());

// extract embeddable python into environment directory
System.IO.Compression.ZipFile.ExtractToDirectory(embeddablePythonZip, environmentDirectory);

// run the pip installer
string pipInstallerPy = Compute.DownloadFile("https://bootstrap.pypa.io/get-pip.py");
string installPipCommand = $"{pythonEnvironment.PythonExecutable()} {pipInstallerPy} && exit";
if (!Compute.RunCommandBool(installPipCommand, hideWindows: true))
{
BH.Engine.Base.Compute.RecordError($"Pip installation did not work using the command {installPipCommand}.");
return null;
}

// remove _pth file
List<string> pthFiles = System.IO.Directory.GetFiles(environmentDirectory, "*.*", SearchOption.TopDirectoryOnly).Where(s => s.EndsWith("._pth")).ToList();
foreach (string pthFile in pthFiles)
{
try
{
File.Delete(pthFile);
}
catch (Exception e)
{
BH.Engine.Base.Compute.RecordError($"{pthFile} not found to be deleted: {e}.");
return null;
}
}

List<PythonPackage> packages = new List<PythonPackage>()
// move PYD and DLL files to DLLs directory
string libDirectory = System.IO.Directory.CreateDirectory(Path.Combine(environmentDirectory, "DLLs")).FullName;
List<string> libFiles = System.IO.Directory.GetFiles(environmentDirectory, "*.*", 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)
{
new PythonPackage(){ Name="pandas", Version="1.3.4" },
new PythonPackage(){ Name="numpy", Version="1.21.3" },
new PythonPackage(){ Name="matplotlib", Version="3.4.3" },
new PythonPackage(){ Name="pymongo", Version="3.12.1" },
new PythonPackage(){ Name="SQLAlchemy", Version="1.4.27" },
new PythonPackage(){ Name="pyodbc", Version="4.0.32" },
};
string newLibFile = Path.Combine(libDirectory, Path.GetFileName(libFile));
try
{
File.Move(libFile, newLibFile);
}
catch (Exception e)
{
BH.Engine.Base.Compute.RecordError($"{libFile} not capable of being moved to {newLibFile}: {e}.");
return null;
}
}

// install packages using Pip
pythonEnvironment = pythonEnvironment.InstallPythonPackages(pythonEnvironment.Packages, force: true, run: true);

return pythonEnvironment;

}

PythonEnvironment pythonEnvironment = Create.PythonEnvironment(Query.ToolkitName(), version, packages);
[Description("Install the default BHoM Python_Toolkit environment.")]
[Input("run", "Set to True to run the PythonEnvironment installer.")]
[Input("force", "If the environment already exists recreate it rather than re-using it.")]
[Input("configJSON", "Path to a config JSON containing Python environment configuration.")]
[Output("pythonEnvironment", "The default BHoM Python_Toolkit environment object.")]
public static PythonEnvironment InstallPythonEnvironment(bool run = false, bool force = false, string configJSON = @"C:\ProgramData\BHoM\Settings\Python\Python_Toolkit.json")
FraserGreenroyd marked this conversation as resolved.
Show resolved Hide resolved
{
// Install base Python environment if it doesnt already exist
PythonEnvironment basePythonEnvironment = Create.PythonEnvironment(@"C:\ProgramData\BHoM\Settings\Python\Python_Toolkit.json");
basePythonEnvironment.InstallPythonEnvironment(force, run);

return Python.Compute.InstallToolkitPythonEnvironment(pythonEnvironment, force, run);
PythonEnvironment toolkitPythonEnvironment = Create.PythonEnvironment(configJSON);
return toolkitPythonEnvironment.InstallPythonEnvironment(force, run);
}
}
}
Expand Down
136 changes: 0 additions & 136 deletions Python_Engine/Compute/InstallToolkitPythonEnvironment.cs

This file was deleted.

37 changes: 37 additions & 0 deletions Python_Engine/Create/PythonEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
using System.IO;
using System.Linq;
using System;
using BH.Adapter.File;

namespace BH.Engine.Python
{
Expand Down Expand Up @@ -60,6 +61,42 @@ public static PythonEnvironment PythonEnvironment(string name, PythonVersion ver
Packages = packages,
};
}

[Description("Create a BHoM Python environment from an environment.json config file.")]
[Input("config", "The path to the environment.json config file.")]
FraserGreenroyd marked this conversation as resolved.
Show resolved Hide resolved
[Output("pythonEnvironment", "A BHoM PythonEnvironment object.")]
public static PythonEnvironment PythonEnvironment(string configJSON)
{
string environmentName = System.IO.Path.GetFileNameWithoutExtension(configJSON);

// load the json
string jsonStr = System.IO.File.ReadAllText(configJSON);
BH.oM.Base.CustomObject obj = (BH.oM.Base.CustomObject)BH.Engine.Serialiser.Convert.FromJson(jsonStr);

string versionString = (string)BH.Engine.Base.Query.PropertyValue(obj.CustomData, "Version");
PythonVersion environmentVersion = (PythonVersion)Enum.Parse(typeof(PythonVersion), $"v{versionString.Replace(".", "_")}");

List<object> pkgObjects = (List<object>)BH.Engine.Base.Query.PropertyValue(obj.CustomData, "Packages");
List<PythonPackage> environmentPackages = new List<PythonPackage>();
foreach (object pkgObject in pkgObjects)
{
string pkgName = (string)BH.Engine.Base.Query.PropertyValue((BH.oM.Base.CustomObject)pkgObject, "Name");
string pkgVersion = (string)BH.Engine.Base.Query.PropertyValue((BH.oM.Base.CustomObject)pkgObject, "Version");
environmentPackages.Add(
new PythonPackage() { Name = pkgName, Version = pkgVersion }
);
}

// populate the PythonEnvironment
PythonEnvironment pythonEnvironment = new PythonEnvironment()
{
Name = environmentName,
Version = environmentVersion,
Packages = environmentPackages
};

return pythonEnvironment;
}
}
}

64 changes: 64 additions & 0 deletions Python_Engine/Create/PythonRequirementsTxt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2022, 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.Python;
using BH.oM.Base.Attributes;

using System.ComponentModel;
using System.Linq;
using System;
using System.Collections.Generic;
using System.Text;

namespace BH.Engine.Python
{
public static partial class Create
{
[Description("Create a requirements.txt file for use in creating a Python envrionment outside of BHoM.")]
[Input("pythonEnvironment", " A BHoM PythonEnvironment object.")]
[Input("directory", "The directory in which the requirements.txt file should be written.")]
[Output("file", "The created requirements.txt file.")]
public static string PythonRequirementsTxt(this PythonEnvironment pythonEnvironment, string directory)
{
return pythonEnvironment.Packages.PythonRequirementsTxt(directory);
}

[Description("Create a requirements.txt file for use in creating a Python envrionment outside of BHoM.")]
[Input("packages", " A list of Python packages to incude in the requirements.txt file.")]
[Input("directory", "The directory in which the requirements.txt file should be written.")]
[Output("file", "The created requirements.txt file.")]
public static string PythonRequirementsTxt(this List<PythonPackage> packages, string directory)
{
string requirementsFile = $"{directory}\\requirements.txt";

StringBuilder sb = new StringBuilder();
foreach (PythonPackage pkg in packages)
{
sb.AppendLine($"{pkg.Name}=={pkg.Version}");
}
System.IO.File.WriteAllText(requirementsFile, sb.ToString());

return requirementsFile;
}
}
}

Loading