From 8fbed44ea85160ba569001e9193db6f48009668d Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Wed, 9 Sep 2026 11:40:43 +0100 Subject: [PATCH 1/7] add UpdateBHoMPackages method --- Python_Engine/Compute/UpdateBHoMPackages.cs | 65 +++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 Python_Engine/Compute/UpdateBHoMPackages.cs diff --git a/Python_Engine/Compute/UpdateBHoMPackages.cs b/Python_Engine/Compute/UpdateBHoMPackages.cs new file mode 100644 index 00000000..b8a8a184 --- /dev/null +++ b/Python_Engine/Compute/UpdateBHoMPackages.cs @@ -0,0 +1,65 @@ +using BH.oM.Python; +using BH.oM.Python.Enums; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Text; + +namespace BH.Engine.Python +{ + public static partial class Compute + { + public static void UpdateBHoMPackages(this string environmentName, PythonVersion version = PythonVersion.Undefined) + { + //construct the expected PythonEnvironment and pass to UpdateBHoMPackages + PythonEnvironment env = new PythonEnvironment() + { + Name = environmentName, + }; + + //python version is only used if the environment name is this toolkits name. + if (environmentName == Query.ToolkitName()) + { + if (version == PythonVersion.Undefined) + { + BH.Engine.Base.Compute.RecordError("The version must be specified when updating BHoM packages for a base python environment. No updates have occurred."); + return; + } + + env.Executable = Path.Combine(Query.DirectoryBaseEnvironment(version), "python.exe"); + } + else + env.Executable = Query.VirtualEnvironmentExecutable(environmentName); + + UpdateBHoMPackages(env); + } + + [Description("Direct method to update BHoM Python editable packages using the PythonEnvironment directly.")] + public static void UpdateBHoMPackages(this PythonEnvironment environment) + { + if (!Query.VirtualEnvironmentExists(environment.Name)) + { + BH.Engine.Base.Compute.RecordError("Given environment does not exist."); + return; + } + + string localPackageDirectory = ResolvePackageDirectory(environment); + + if (!Directory.Exists(localPackageDirectory)) + { + BH.Engine.Base.Compute.RecordError($"There is no local package directory for {environment.Name} (searched at \"{localPackageDirectory}\"). No packages were updated."); + return; + } + + InstallPackageLocal(environment, localPackageDirectory); + } + + private static string ResolvePackageDirectory(PythonEnvironment environment) + { + string packageName = environment.Name; + string codeDirectory = Query.DirectoryCode(); + return Path.Combine(codeDirectory, packageName); + } + } +} \ No newline at end of file From f201b603caa680daff8cc3d014e6bda0a2956278 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Wed, 9 Sep 2026 11:45:42 +0100 Subject: [PATCH 2/7] add documentation attributes --- Python_Engine/Compute/UpdateBHoMPackages.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Python_Engine/Compute/UpdateBHoMPackages.cs b/Python_Engine/Compute/UpdateBHoMPackages.cs index b8a8a184..1814a07a 100644 --- a/Python_Engine/Compute/UpdateBHoMPackages.cs +++ b/Python_Engine/Compute/UpdateBHoMPackages.cs @@ -1,4 +1,5 @@ -using BH.oM.Python; +using BH.oM.Base.Attributes; +using BH.oM.Python; using BH.oM.Python.Enums; using System; using System.Collections.Generic; @@ -10,6 +11,9 @@ namespace BH.Engine.Python { public static partial class Compute { + [Description("Using the environment name (and version if the environment name is the base python environment), directly install/update missing BHoM packages.")] + [Input("environmentName", "The name of the environment to update.")] + [Input("version", "If the environment name is the name of the base python environment, the version that needs updating.")] public static void UpdateBHoMPackages(this string environmentName, PythonVersion version = PythonVersion.Undefined) { //construct the expected PythonEnvironment and pass to UpdateBHoMPackages @@ -35,7 +39,10 @@ public static void UpdateBHoMPackages(this string environmentName, PythonVersion UpdateBHoMPackages(env); } - [Description("Direct method to update BHoM Python editable packages using the PythonEnvironment directly.")] + /***************************************************/ + + [Description("Using a PythonEnvironment, directly install/update missing BHoM packages.")] + [Input("environment", "The python environment to update.")] public static void UpdateBHoMPackages(this PythonEnvironment environment) { if (!Query.VirtualEnvironmentExists(environment.Name)) @@ -55,6 +62,8 @@ public static void UpdateBHoMPackages(this PythonEnvironment environment) InstallPackageLocal(environment, localPackageDirectory); } + /***************************************************/ + private static string ResolvePackageDirectory(PythonEnvironment environment) { string packageName = environment.Name; From 2e207eb88f1a8445d31ad0fbb727e890f36d85b9 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Wed, 9 Sep 2026 12:05:05 +0100 Subject: [PATCH 3/7] copyright compliance --- Python_Engine/Compute/UpdateBHoMPackages.cs | 24 ++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Python_Engine/Compute/UpdateBHoMPackages.cs b/Python_Engine/Compute/UpdateBHoMPackages.cs index 1814a07a..c4b58e47 100644 --- a/Python_Engine/Compute/UpdateBHoMPackages.cs +++ b/Python_Engine/Compute/UpdateBHoMPackages.cs @@ -1,4 +1,26 @@ -using BH.oM.Base.Attributes; +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, 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 . + */ + +using BH.oM.Base.Attributes; using BH.oM.Python; using BH.oM.Python.Enums; using System; From 607e4bd90d87ece6990a2b1952c9e2c7d499f605 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Wed, 9 Sep 2026 12:28:29 +0100 Subject: [PATCH 4/7] check executable directly instead. --- Python_Engine/Compute/UpdateBHoMPackages.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python_Engine/Compute/UpdateBHoMPackages.cs b/Python_Engine/Compute/UpdateBHoMPackages.cs index c4b58e47..2c2577dc 100644 --- a/Python_Engine/Compute/UpdateBHoMPackages.cs +++ b/Python_Engine/Compute/UpdateBHoMPackages.cs @@ -67,9 +67,9 @@ public static void UpdateBHoMPackages(this string environmentName, PythonVersion [Input("environment", "The python environment to update.")] public static void UpdateBHoMPackages(this PythonEnvironment environment) { - if (!Query.VirtualEnvironmentExists(environment.Name)) + if (!File.Exists(environment.Executable)) { - BH.Engine.Base.Compute.RecordError("Given environment does not exist."); + BH.Engine.Base.Compute.RecordError($"Given environment or base install {environment.Executable} does not exist."); return; } From 8d38f59c107c2b596113f35c46ae36f71a56fe65 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Wed, 9 Sep 2026 15:33:14 +0100 Subject: [PATCH 5/7] use pip based check for dependencies to be updated --- Python_Engine/Compute/UpdateBHoMPackages.cs | 38 ++++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/Python_Engine/Compute/UpdateBHoMPackages.cs b/Python_Engine/Compute/UpdateBHoMPackages.cs index 2c2577dc..0b411ec1 100644 --- a/Python_Engine/Compute/UpdateBHoMPackages.cs +++ b/Python_Engine/Compute/UpdateBHoMPackages.cs @@ -20,13 +20,16 @@ * along with this code. If not, see . */ +using BH.oM.Base; using BH.oM.Base.Attributes; using BH.oM.Python; using BH.oM.Python.Enums; using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.IO; +using System.Linq; using System.Text; namespace BH.Engine.Python @@ -73,24 +76,33 @@ public static void UpdateBHoMPackages(this PythonEnvironment environment) return; } - string localPackageDirectory = ResolvePackageDirectory(environment); + //`python -m pip list -e --format json` lists all environment packages that are editable installs. + //As all BHoM packages are editable installs, this handily lists all the packages that should be updated if necessary. + //side effect of updating other editable installs if they have changed, but I suspect this won't be a problem as anyone who has these would update packages manually instead. + System.Diagnostics.Process process = new System.Diagnostics.Process() + { + StartInfo = new System.Diagnostics.ProcessStartInfo() + { + FileName = environment.Executable, + Arguments = $"-m pip list -e --format json", + UseShellExecute = false, + RedirectStandardError = true, + } + }; + + process.StartInfo.Environment["PYTHONHOME"] = ""; + string stdOut; - if (!Directory.Exists(localPackageDirectory)) + using (Process p = Process.Start(process.StartInfo)) { - BH.Engine.Base.Compute.RecordError($"There is no local package directory for {environment.Name} (searched at \"{localPackageDirectory}\"). No packages were updated."); - return; + stdOut = p.StandardOutput.ReadToEnd(); + p.WaitForExit(); } - InstallPackageLocal(environment, localPackageDirectory); - } + IEnumerable objs = Serialiser.Convert.FromJsonArray(stdOut).OfType(); - /***************************************************/ - - private static string ResolvePackageDirectory(PythonEnvironment environment) - { - string packageName = environment.Name; - string codeDirectory = Query.DirectoryCode(); - return Path.Combine(codeDirectory, packageName); + foreach (CustomObject obj in objs) + InstallPackageLocal(environment, (string)obj.CustomData["editable_project_location"]); } } } \ No newline at end of file From 81699b29e576c76c09a64d2f032d1e870f9b7ef4 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Thu, 10 Sep 2026 11:44:31 +0100 Subject: [PATCH 6/7] ensure that when getting environments, they are fully updated. --- Python_Engine/Compute/BasePythonEnvironment.cs | 6 +++++- Python_Engine/Compute/UpdateBHoMPackages.cs | 2 +- Python_Engine/Compute/VirtualEnvironment.cs | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Python_Engine/Compute/BasePythonEnvironment.cs b/Python_Engine/Compute/BasePythonEnvironment.cs index fa90129e..8ec03656 100644 --- a/Python_Engine/Compute/BasePythonEnvironment.cs +++ b/Python_Engine/Compute/BasePythonEnvironment.cs @@ -60,7 +60,11 @@ public static PythonEnvironment BasePythonEnvironment( bool exists = File.Exists(targetExecutable); if (exists && reload) - return new PythonEnvironment() { Name = Query.ToolkitName(), Executable = targetExecutable }; + { + PythonEnvironment env = new PythonEnvironment() { Name = Query.ToolkitName(), Executable = targetExecutable }; + UpdateBHoMPackages(env); + return env; + } if (exists && !reload) // remove all existing environments and kernels diff --git a/Python_Engine/Compute/UpdateBHoMPackages.cs b/Python_Engine/Compute/UpdateBHoMPackages.cs index 0b411ec1..e1133531 100644 --- a/Python_Engine/Compute/UpdateBHoMPackages.cs +++ b/Python_Engine/Compute/UpdateBHoMPackages.cs @@ -86,7 +86,7 @@ public static void UpdateBHoMPackages(this PythonEnvironment environment) FileName = environment.Executable, Arguments = $"-m pip list -e --format json", UseShellExecute = false, - RedirectStandardError = true, + RedirectStandardOutput = true, } }; diff --git a/Python_Engine/Compute/VirtualEnvironment.cs b/Python_Engine/Compute/VirtualEnvironment.cs index 1e02dad5..6ed9dde7 100644 --- a/Python_Engine/Compute/VirtualEnvironment.cs +++ b/Python_Engine/Compute/VirtualEnvironment.cs @@ -60,7 +60,11 @@ public static PythonEnvironment VirtualEnvironment(this PythonVersion version, s bool exists = Query.VirtualEnvironmentExists(name); if (exists && reload) - return new PythonEnvironment() { Name = name, Executable = targetExecutable }; + { + PythonEnvironment env = new PythonEnvironment() { Name = name, Executable = targetExecutable }; + UpdateBHoMPackages(env); + return env; + } if (exists && !reload) { From 41c39a853359e86912fb31c3cb48397bc129ddb5 Mon Sep 17 00:00:00 2001 From: Tom Kingstone Date: Thu, 10 Sep 2026 12:59:03 +0100 Subject: [PATCH 7/7] remove carriage return from stdout before reading it as a json array --- Python_Engine/Compute/UpdateBHoMPackages.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python_Engine/Compute/UpdateBHoMPackages.cs b/Python_Engine/Compute/UpdateBHoMPackages.cs index e1133531..c1f3024f 100644 --- a/Python_Engine/Compute/UpdateBHoMPackages.cs +++ b/Python_Engine/Compute/UpdateBHoMPackages.cs @@ -99,6 +99,8 @@ public static void UpdateBHoMPackages(this PythonEnvironment environment) p.WaitForExit(); } + stdOut = stdOut.TrimEnd('\r', '\n'); + IEnumerable objs = Serialiser.Convert.FromJsonArray(stdOut).OfType(); foreach (CustomObject obj in objs)