Skip to content

Commit e9c9557

Browse files
9.3 Deployment (#249)
2 parents 4c27b6e + f8a63a6 commit e9c9557

41 files changed

Lines changed: 2148 additions & 819 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci-beta.yml‎

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Beta-tier proxy, BHoM variant. Bundles the tier's checks plus
2+
# copyright-compliance (BHoM enforces OSS copyright; the BHE variant omits it).
3+
# Copy to .github/workflows/ci-beta.yml in each BHoM beta repo.
4+
# Runs on pull_request, non-blocking until a ruleset requires its checks.
5+
# Workflow name CI keeps check contexts bare: required contexts are the job names.
6+
7+
name: CI
8+
9+
on:
10+
pull_request:
11+
branches:
12+
- develop
13+
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
ci-build:
20+
runs-on: windows-2025-vs2026
21+
timeout-minutes: 30
22+
steps:
23+
- name: Run build
24+
uses: BHoM/CI_Toolkit/.github/actions/ci-build@develop
25+
with:
26+
app_id: ${{ secrets.BHOM_APP_ID }}
27+
private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }}
28+
29+
ci-code-compliance:
30+
runs-on: windows-2025-vs2026
31+
timeout-minutes: 20
32+
permissions:
33+
contents: read
34+
pull-requests: read
35+
actions: write
36+
steps:
37+
- name: Run code compliance
38+
uses: BHoM/CI_Toolkit/.github/actions/ci-compliance@develop
39+
with:
40+
check_type: code
41+
patterns: '*.cs'
42+
app_id: ${{ secrets.BHOM_APP_ID }}
43+
private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }}
44+
45+
ci-copyright-compliance:
46+
runs-on: windows-2025-vs2026
47+
timeout-minutes: 20
48+
permissions:
49+
contents: read
50+
pull-requests: read
51+
actions: write
52+
steps:
53+
- name: Run copyright compliance
54+
uses: BHoM/CI_Toolkit/.github/actions/ci-compliance@develop
55+
with:
56+
check_type: copyright
57+
patterns: '*.cs'
58+
app_id: ${{ secrets.BHOM_APP_ID }}
59+
private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }}
60+
61+
ci-dataset-compliance:
62+
runs-on: windows-2025-vs2026
63+
timeout-minutes: 20
64+
permissions:
65+
contents: read
66+
pull-requests: read
67+
actions: write
68+
steps:
69+
- name: Run dataset compliance
70+
uses: BHoM/CI_Toolkit/.github/actions/ci-compliance@develop
71+
with:
72+
check_type: dataset
73+
patterns: ':(icase)*datasets*.json'
74+
app_id: ${{ secrets.BHOM_APP_ID }}
75+
private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }}
76+
77+
ci-documentation-compliance:
78+
runs-on: windows-2025-vs2026
79+
timeout-minutes: 20
80+
permissions:
81+
contents: read
82+
pull-requests: read
83+
actions: write
84+
steps:
85+
- name: Run documentation compliance
86+
uses: BHoM/CI_Toolkit/.github/actions/ci-compliance@develop
87+
with:
88+
check_type: documentation
89+
patterns: '*.cs'
90+
app_id: ${{ secrets.BHOM_APP_ID }}
91+
private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }}
92+
93+
ci-project-compliance:
94+
runs-on: windows-2025-vs2026
95+
timeout-minutes: 20
96+
permissions:
97+
contents: read
98+
pull-requests: read
99+
actions: write
100+
steps:
101+
- name: Run project compliance
102+
uses: BHoM/CI_Toolkit/.github/actions/ci-compliance@develop
103+
with:
104+
check_type: project
105+
patterns: '*AssemblyInfo.cs *.csproj'
106+
app_id: ${{ secrets.BHOM_APP_ID }}
107+
private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }}
108+
109+
ci-dataset-tests:
110+
runs-on: windows-2025-vs2026
111+
timeout-minutes: 30
112+
steps:
113+
- name: Run dataset tests
114+
uses: BHoM/CI_Toolkit/.github/actions/ci-dataset-tests@develop
115+
with:
116+
app_id: ${{ secrets.BHOM_APP_ID }}
117+
private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }}
118+
119+
ci-serialisation:
120+
runs-on: windows-2025-vs2026
121+
timeout-minutes: 90
122+
steps:
123+
- name: Run serialisation
124+
uses: BHoM/CI_Toolkit/.github/actions/ci-serialisation@develop
125+
with:
126+
app_id: ${{ secrets.BHOM_APP_ID }}
127+
private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }}
128+
base_ref: ${{ github.base_ref }}
129+
130+
ci-versioning:
131+
runs-on: windows-2025-vs2026
132+
timeout-minutes: 90
133+
steps:
134+
- name: Run versioning
135+
uses: BHoM/CI_Toolkit/.github/actions/ci-versioning@develop
136+
with:
137+
app_id: ${{ secrets.BHOM_APP_ID }}
138+
private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }}

‎Python_Engine/Compute/BasePythonEnvironment.cs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ public static PythonEnvironment BasePythonEnvironment(
6060
bool exists = File.Exists(targetExecutable);
6161

6262
if (exists && reload)
63-
return new PythonEnvironment() { Name = Query.ToolkitName(), Executable = targetExecutable };
63+
{
64+
PythonEnvironment env = new PythonEnvironment() { Name = Query.ToolkitName(), Executable = targetExecutable };
65+
UpdateBHoMPackages(env);
66+
return env;
67+
}
6468

6569
if (exists && !reload)
6670
// remove all existing environments and kernels
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* This file is part of the Buildings and Habitats object Model (BHoM)
3+
* Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
4+
*
5+
* Each contributor holds copyright over their respective contributions.
6+
* The project versioning (Git) records all such contribution source information.
7+
*
8+
*
9+
* The BHoM is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Lesser General Public License as published by
11+
* the Free Software Foundation, either version 3.0 of the License, or
12+
* (at your option) any later version.
13+
*
14+
* The BHoM is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Lesser General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public License
20+
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
21+
*/
22+
23+
using BH.oM.Base;
24+
using BH.oM.Base.Attributes;
25+
using BH.oM.Python;
26+
using BH.oM.Python.Enums;
27+
using System;
28+
using System.Collections.Generic;
29+
using System.ComponentModel;
30+
using System.Diagnostics;
31+
using System.IO;
32+
using System.Linq;
33+
using System.Text;
34+
35+
namespace BH.Engine.Python
36+
{
37+
public static partial class Compute
38+
{
39+
[Description("Using the environment name (and version if the environment name is the base python environment), directly install/update missing BHoM packages.")]
40+
[Input("environmentName", "The name of the environment to update.")]
41+
[Input("version", "If the environment name is the name of the base python environment, the version that needs updating.")]
42+
public static void UpdateBHoMPackages(this string environmentName, PythonVersion version = PythonVersion.Undefined)
43+
{
44+
//construct the expected PythonEnvironment and pass to UpdateBHoMPackages
45+
PythonEnvironment env = new PythonEnvironment()
46+
{
47+
Name = environmentName,
48+
};
49+
50+
//python version is only used if the environment name is this toolkits name.
51+
if (environmentName == Query.ToolkitName())
52+
{
53+
if (version == PythonVersion.Undefined)
54+
{
55+
BH.Engine.Base.Compute.RecordError("The version must be specified when updating BHoM packages for a base python environment. No updates have occurred.");
56+
return;
57+
}
58+
59+
env.Executable = Path.Combine(Query.DirectoryBaseEnvironment(version), "python.exe");
60+
}
61+
else
62+
env.Executable = Query.VirtualEnvironmentExecutable(environmentName);
63+
64+
UpdateBHoMPackages(env);
65+
}
66+
67+
/***************************************************/
68+
69+
[Description("Using a PythonEnvironment, directly install/update missing BHoM packages.")]
70+
[Input("environment", "The python environment to update.")]
71+
public static void UpdateBHoMPackages(this PythonEnvironment environment)
72+
{
73+
if (!File.Exists(environment.Executable))
74+
{
75+
BH.Engine.Base.Compute.RecordError($"Given environment or base install {environment.Executable} does not exist.");
76+
return;
77+
}
78+
79+
//`python -m pip list -e --format json` lists all environment packages that are editable installs.
80+
//As all BHoM packages are editable installs, this handily lists all the packages that should be updated if necessary.
81+
//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.
82+
System.Diagnostics.Process process = new System.Diagnostics.Process()
83+
{
84+
StartInfo = new System.Diagnostics.ProcessStartInfo()
85+
{
86+
FileName = environment.Executable,
87+
Arguments = $"-m pip list -e --format json",
88+
UseShellExecute = false,
89+
RedirectStandardOutput = true,
90+
}
91+
};
92+
93+
process.StartInfo.Environment["PYTHONHOME"] = "";
94+
string stdOut;
95+
96+
using (Process p = Process.Start(process.StartInfo))
97+
{
98+
stdOut = p.StandardOutput.ReadToEnd();
99+
p.WaitForExit();
100+
}
101+
102+
stdOut = stdOut.TrimEnd('\r', '\n');
103+
104+
IEnumerable<CustomObject> objs = Serialiser.Convert.FromJsonArray(stdOut).OfType<CustomObject>();
105+
106+
foreach (CustomObject obj in objs)
107+
InstallPackageLocal(environment, (string)obj.CustomData["editable_project_location"]);
108+
}
109+
}
110+
}

‎Python_Engine/Compute/VirtualEnvironment.cs‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ public static PythonEnvironment VirtualEnvironment(this PythonVersion version, s
6060
bool exists = Query.VirtualEnvironmentExists(name);
6161

6262
if (exists && reload)
63-
return new PythonEnvironment() { Name = name, Executable = targetExecutable };
63+
{
64+
PythonEnvironment env = new PythonEnvironment() { Name = name, Executable = targetExecutable };
65+
UpdateBHoMPackages(env);
66+
return env;
67+
}
6468

6569
if (exists && !reload)
6670
{

‎Python_Engine/Python/Dockerfile‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
11
FROM python:3.10-slim
2-
32
COPY . /Python_Toolkit
43

4+
#requires build argument --build-context bhom-assemblies C:/ProgramData/BHoM/Assemblies
5+
COPY --from=bhom-assemblies . /bin/BHoM/Assemblies
6+
7+
#install .net core 8.0, and set the pythonnet runtime to the .net core CLR
8+
RUN apt-get update \
9+
&& apt-get install -y wget
10+
11+
RUN wget https://packages.microsoft.com/config/debian/13/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
12+
RUN dpkg -i packages-microsoft-prod.deb
13+
RUN rm packages-microsoft-prod.deb
14+
15+
RUN apt-get update \
16+
&& apt-get install -y aspnetcore-runtime-10.0
17+
18+
ENV PYTHONNET_RUNTIME="coreclr"
19+
520
RUN pip3 install ./Python_Toolkit
621

722
RUN rm ./Python_Toolkit -rf

‎Python_Engine/Python/pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies = [
1616
"pytest-cov>=6.0.0",
1717
"pytest-order",
1818
"virtualenv",
19+
"pythonnet",
1920
]
2021

2122
[urls]

‎Python_Engine/Python/src/python_toolkit/bhom/__init__.py‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@
66
import tempfile
77
import importlib.metadata
88

9+
if os.name == 'nt':
10+
from .installer_info import INSTALLER_INFO
11+
else:
12+
INSTALLER_INFO = {"Version": importlib.metadata.version("python_toolkit")}
13+
914
BHOM_LOG_FOLDER = Path(path.expandvars("%PROGRAMDATA%/BHoM/Logs"))
15+
TEMP_LOG_FOLDER = Path(tempfile.gettempdir()) / "BHoM" / "Logs"
1016
TOOLKIT_NAME = "Python_Toolkit"
11-
BHOM_VERSION = importlib.metadata.version("python_toolkit")
17+
BHOM_VERSION = INSTALLER_INFO["Version"]
1218

1319
#Environment variable that if set disables BHoM analytics logging.
1420
DISABLE_ANALYTICS = os.environ.get("DISABLE_BHOM_ANALYTICS", None)
@@ -18,5 +24,18 @@
1824
DISABLE_ANALYTICS = True
1925

2026
if not BHOM_LOG_FOLDER.exists():
21-
BHOM_LOG_FOLDER = Path(tempfile.gettempdir()) / "BHoM" / "Logs"
22-
BHOM_LOG_FOLDER.mkdir(exist_ok=True, parents=True)
27+
28+
try:
29+
BHOM_LOG_FOLDER.mkdir(exist_ok=True, parents=True)
30+
31+
#migration recovery for any logs in the temp folder
32+
if TEMP_LOG_FOLDER.exists():
33+
for file in TEMP_LOG_FOLDER.glob("*.log"):
34+
file.rename(BHOM_LOG_FOLDER / file.name)
35+
36+
except Exception as e:
37+
BHOM_LOG_FOLDER = TEMP_LOG_FOLDER
38+
BHOM_LOG_FOLDER.mkdir(exist_ok=True, parents=True)
39+
40+
41+

‎Python_Engine/Python/src/python_toolkit/bhom/analytics.py‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ def summarise_usage_logs(usage_log_entries:List[UsageLogEntry]) -> List[Dict]:
7171

7272
usage_log_entries.sort(key=lambda x: x.ProjectID)
7373

74+
short_bhom_version = ".".join(BHOM_VERSION.split(".", 2)[0:2])
75+
7476
for file_id, filegroup in groupby(usage_log_entries, lambda x: x.FileId):
7577
filegroup = list(filegroup)
7678
project_id = filegroup[0].ProjectID
@@ -89,15 +91,17 @@ def summarise_usage_logs(usage_log_entries:List[UsageLogEntry]) -> List[Dict]:
8991
"CallerName": first_entry.CallerName,
9092
"SelectedItem": first_entry.SelectedItem,
9193
"Computer": socket.gethostname(),
92-
"UserName": os.environ.get("USERNAME"),
93-
"BHoMVersion": BHOM_VERSION,
94+
"Username": os.environ.get("USERNAME"),
95+
"BHoMVersion": short_bhom_version,
9496
"FileId": file_id,
9597
"FileName": filename,
9698
"ProjectID": project_id,
9799
"NbCallingComponents": len(set([a.ComponentId for a in methodgroup])),
98-
"TotalNbCals": len(methodgroup),
100+
"TotalNbCalls": len(methodgroup),
99101
"Errors": list(itertools.chain.from_iterable([x.Errors for x in methodgroup])),
100-
"_t": "BH.oM.BHoMAnalytics.UsageEntry"
102+
"_t": "BH.oM.BHoMAnalytics.UsageEntry",
103+
"__Time__": datetime.now(),
104+
"_bhomVersion": short_bhom_version
101105
})
102106

103107
return db_entries
@@ -150,7 +154,7 @@ def decorator(function: Callable):
150154
@wraps(function)
151155
def wrapper(*args, **kwargs) -> Any:
152156
"""A wrapper around the function that captures usage analytics."""
153-
157+
154158
if disable:
155159
CONSOLE_LOGGER.debug("bhom_analytics is curently disabled.")
156160
return function(*args, **kwargs)

0 commit comments

Comments
 (0)