Skip to content

Commit d95c474

Browse files
committed
Merge branch 'main' into release/0.3
2 parents a4c81e0 + f04ce11 commit d95c474

File tree

7 files changed

+329
-16
lines changed

7 files changed

+329
-16
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Autoupdate Python Versions
2+
on:
3+
workflow_dispatch:
4+
schedule:
5+
- cron: '0 0 * * *'
6+
# Or if the "scripts" directory is modified
7+
push:
8+
paths:
9+
- 'scripts/**'
10+
11+
12+
env:
13+
MAIN_PYTHON_VERSION: '3.12'
14+
15+
concurrency:
16+
group: ${{ github.workflow }}-${{ github.ref }}
17+
cancel-in-progress: true
18+
19+
jobs:
20+
autoupdate-python-versions:
21+
name: Autoupdate Python Versions
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@v4
26+
with:
27+
token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }}
28+
29+
- name: Set up Python ${{ env.MAIN_PYTHON_VERSION }}
30+
uses: actions/setup-python@v5
31+
with:
32+
python-version: ${{ env.MAIN_PYTHON_VERSION }}
33+
34+
- name: Run the autoupdate script
35+
run: |
36+
python -m pip install --upgrade pip
37+
pip install -r scripts/requirements.txt
38+
python scripts/update_python_versions.py
39+
40+
- name: Commit changes (if any)
41+
run: |
42+
# Configure git username & email
43+
git config user.name 'pyansys-ci-bot'
44+
git config user.email '[email protected]'
45+
46+
# Check if there are any changes
47+
diff_code=$(git diff --exit-code)
48+
49+
# If there are changes, verify whether the branch 'feat/update-python-version' exists
50+
# If it does, switch to it, rebase it on top of the main branch, and push the changes
51+
# If it doesn't, create the branch, commit the changes, and push the branch
52+
if [ -n "$diff_code" ]; then
53+
if git show-ref --verify --quiet refs/heads/feat/update-python-version; then
54+
git checkout feat/update-python-version
55+
git rebase main
56+
else
57+
git checkout -b feat/update-python-version
58+
fi
59+
60+
git add .
61+
git commit -m "chore: update Python versions"
62+
git push origin feat/update-python-version
63+
fi
64+
65+
# If there are changes, create a pull request
66+
if [ -n "$diff_code" ]; then
67+
gh pr create --title "chore: update Python versions" --body "This PR updates the Python versions in the CI configuration files." --base main --head feat/update-python-version
68+
fi

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ repos:
1313
- --line-length=88
1414

1515
- repo: https://github.com/psf/black
16-
rev: 24.3.0
16+
rev: 24.4.0
1717
hooks:
1818
- id: black
1919
args:
@@ -54,7 +54,7 @@ repos:
5454

5555
# this validates our github workflow files
5656
- repo: https://github.com/python-jsonschema/check-jsonschema
57-
rev: 0.28.1
57+
rev: 0.28.2
5858
hooks:
5959
- id: check-github-workflows
6060

scripts/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests==2.31.0
2+
packaging==23.2

scripts/update_python_versions.py

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
"""Script that updates the Python versions used in the project."""
2+
3+
import os
4+
import re
5+
6+
from packaging.version import Version
7+
import requests
8+
9+
10+
def is_version_string(s: str) -> bool:
11+
"""Check if the string is in accepted version format.
12+
13+
Parameters
14+
----------
15+
s : str
16+
String to check.
17+
18+
Returns
19+
-------
20+
bool
21+
True if the string is in the accepted version format, False otherwise.
22+
"""
23+
pattern = r"^\d+\.\d+\.\d+$"
24+
return bool(re.match(pattern, s))
25+
26+
27+
def get_latest_github_release(user, repo) -> dict:
28+
"""Get the latest release of a GitHub repository.
29+
30+
Parameters
31+
----------
32+
user : str
33+
GitHub username.
34+
repo : str
35+
Repository name.
36+
37+
Returns
38+
-------
39+
dict
40+
JSON response of the latest release.
41+
"""
42+
url = f"https://api.github.com/repos/{user}/{repo}/releases/latest"
43+
response = requests.get(url)
44+
if response.status_code == 200:
45+
return response.json()
46+
else:
47+
print(f"Failed to get releases: {response.content}")
48+
return None
49+
50+
51+
def get_minor_version_sublist_with_greater_patch(list: list[str], current_version: str):
52+
"""Get the sublist of versions with greater patch than the current version."""
53+
major, minor, patch = current_version.split(".")
54+
major, minor, patch = int(major), int(minor), int(patch)
55+
sublist = [version for version in list if version.startswith(f"{major}.{minor}.")]
56+
sublist = [version for version in sublist if int(version.split(".")[2]) > patch]
57+
sublist = sorted(sublist, key=Version, reverse=True)
58+
59+
return sublist
60+
61+
62+
# Get path to the root of the project
63+
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
64+
65+
# Path to the constants file
66+
CONSTANTS_FILE = os.path.join(
67+
ROOT_DIR, "src", "ansys", "tools", "installer", "constants.py"
68+
)
69+
70+
# Parse the constants file to find the current Python versions
71+
# used in the project. The versions are stored in a tuple
72+
with open(CONSTANTS_FILE, "r") as f:
73+
lines = f.readlines()
74+
75+
# Import the following constants inside the constants file
76+
#
77+
# Example:
78+
#
79+
# VANILLA_PYTHON_VERSIONS = {
80+
# "Python 3.8": "3.8.10",
81+
# "Python 3.9": "3.9.13",
82+
# "Python 3.10": "3.10.11",
83+
# "Python 3.11": "3.11.6",
84+
# "Python 3.12": "3.12.0",
85+
# }
86+
#
87+
# CONDA_PYTHON_VERSION = "23.1.0-4"
88+
#
89+
90+
vanilla_python_versions: dict[str, str] = {}
91+
conda_python_version: str = ""
92+
93+
for line in lines:
94+
if "VANILLA_PYTHON_VERSIONS" in line:
95+
# Store the index of the line where the dictionary starts
96+
start_index = lines.index(line)
97+
break
98+
99+
# Get the dictionary that contains the Python versions
100+
for line in lines[start_index:]:
101+
if "}" in line:
102+
# Store the index of the line where the dictionary ends
103+
end_index = lines.index(line, start_index)
104+
break
105+
106+
# Parse the dictionary to get the Python versions
107+
for line in lines[start_index : end_index + 1]:
108+
if "Python" in line:
109+
# Extract the Python version and the version number
110+
python_version = line.split(":")[0].strip().replace('"', "")
111+
version_number = line.split(":")[1].strip().replace('"', "").replace(",", "")
112+
113+
# Store the Python version and the version number
114+
vanilla_python_versions[python_version] = version_number
115+
116+
# Get the Conda Python version
117+
for line in lines:
118+
if "CONDA_PYTHON_VERSION" in line:
119+
conda_python_version = line.split("=")[1].strip().replace('"', "")
120+
121+
# LOG - Print the current Python versions
122+
print("Current Vanilla Python versions:")
123+
for version in vanilla_python_versions.values():
124+
print(f">>> '{version}'")
125+
126+
print("Current Conda Python version")
127+
print(f">>> '{conda_python_version}'")
128+
129+
# --------------------------------------------------------------------------------------------
130+
131+
print("--- \nUpdating Python versions...\n")
132+
133+
# Check remote Python versions available
134+
PYTHON_FTP = "https://www.python.org/ftp/python"
135+
136+
# List all folders in the Python FTP
137+
response = requests.get(PYTHON_FTP)
138+
text = response.text.split("\n")
139+
ftp_versions = []
140+
for line in text:
141+
tmp = line.strip('<a href="').split('/">')[0]
142+
# Check if the folder is a Python version
143+
if is_version_string(tmp):
144+
ftp_versions.append(tmp)
145+
146+
# For each minor version, get the patch versions available
147+
# greter than the current patch version
148+
for python_version_key, python_version_value in vanilla_python_versions.items():
149+
# Get the minor version of the current Python version
150+
minor_version = ".".join(python_version_value.split(".")[:2])
151+
152+
# Get the patch versions available
153+
patch_versions = get_minor_version_sublist_with_greater_patch(
154+
ftp_versions, python_version_value
155+
)
156+
157+
# Check if the patch versions contain the executable
158+
new_patch_version = None
159+
for patch_version in patch_versions:
160+
# Check if the executable exists
161+
response_1 = requests.get(
162+
f"{PYTHON_FTP}/{patch_version}/Python-{patch_version}.tar.xz"
163+
)
164+
response_2 = requests.get(
165+
f"{PYTHON_FTP}/{patch_version}/python-{patch_version}-amd64.exe"
166+
)
167+
if response_1.status_code == 200 and response_2.status_code == 200:
168+
print(f"Python {patch_version} is available for download")
169+
new_patch_version = patch_version
170+
break
171+
172+
# Update the Python version
173+
if new_patch_version:
174+
vanilla_python_versions[python_version_key] = new_patch_version
175+
else:
176+
print(f"Python {python_version_value} is already the latest version available")
177+
178+
# Get the latest Conda Python version
179+
latest_conda_release = get_latest_github_release("conda-forge", "miniforge")
180+
181+
# Verify that the assets are available
182+
assets = latest_conda_release["assets"]
183+
new_conda_version = None
184+
count = 0
185+
for asset in assets:
186+
if f"Miniforge3-{latest_conda_release['name']}-Linux-x86_64.sh" in asset["name"]:
187+
count += 1
188+
if f"Miniforge3-{latest_conda_release['name']}-Windows-x86_64.exe" in asset["name"]:
189+
count += 1
190+
if count == 2:
191+
new_conda_version = latest_conda_release["name"]
192+
break
193+
194+
# Update the Conda Python version
195+
if new_conda_version:
196+
conda_python_version = new_conda_version
197+
print(f"Conda Python version updated to {conda_python_version}")
198+
else:
199+
print(f"Conda Python version is already the latest version available")
200+
201+
print("\nPython versions updated successfully\n ---")
202+
203+
# --------------------------------------------------------------------------------------------
204+
205+
# LOG - Print the new Python versions
206+
print("New Vanilla Python versions:")
207+
for version in vanilla_python_versions.values():
208+
print(f">>> '{version}'")
209+
210+
print("New Conda Python version:")
211+
print(f">>> '{conda_python_version}'")
212+
213+
# Update the constants file with the new Python versions
214+
# Write the new Python versions to the constants file
215+
with open(CONSTANTS_FILE, "w") as f:
216+
for line in lines[:start_index]:
217+
f.write(line)
218+
219+
f.write("VANILLA_PYTHON_VERSIONS = {\n")
220+
for python_version, version_number in vanilla_python_versions.items():
221+
f.write(f' "{python_version}": "{version_number}",\n')
222+
f.write("}\n\n")
223+
224+
f.write(f'CONDA_PYTHON_VERSION = "{conda_python_version}"\n')

src/ansys/tools/installer/constants.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,21 @@
180180

181181
VENV_DEFAULT_PATH = "venv_default_path"
182182
VENV_SEARCH_PATH = "venv_search_path"
183+
184+
185+
###############################################################################
186+
# Python versions
187+
###############################################################################
188+
#
189+
# Do not modify below this section
190+
#
191+
192+
VANILLA_PYTHON_VERSIONS = {
193+
"Python 3.8": "3.8.10",
194+
"Python 3.9": "3.9.13",
195+
"Python 3.10": "3.10.11",
196+
"Python 3.11": "3.11.9",
197+
"Python 3.12": "3.12.3",
198+
}
199+
200+
CONDA_PYTHON_VERSION = "24.1.2-0"

src/ansys/tools/installer/main.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,13 @@
4242
ABOUT_TEXT,
4343
ANSYS_FAVICON,
4444
ASSETS_PATH,
45+
CONDA_PYTHON_VERSION,
4546
INSTALL_TEXT,
4647
LOG,
4748
PRE_COMPILED_PYTHON_WARNING,
4849
PYTHON_VERSION_TEXT,
4950
UNABLE_TO_RETRIEVE_LATEST_VERSION_TEXT,
51+
VANILLA_PYTHON_VERSIONS,
5052
)
5153
from ansys.tools.installer.create_virtual_environment import CreateVenvTab
5254
from ansys.tools.installer.installed_table import InstalledTab
@@ -224,14 +226,14 @@ def __init__(self, show=True):
224226
python_version.setLayout(python_version_layout)
225227

226228
self.python_version_select = QtWidgets.QComboBox()
227-
self.python_version_select.addItem("Python 3.8", "3.8.10")
228-
self.python_version_select.addItem("Python 3.9", "3.9.13")
229-
self.python_version_select.addItem("Python 3.10", "3.10.11")
230-
self.python_version_select.addItem("Python 3.11", "3.11.6")
231-
self.python_version_select.addItem("Python 3.12", "3.12.0")
232-
233-
# Set the default selection to "Python 3.11"
234-
default_index = self.python_version_select.findText("Python 3.11")
229+
for elem_key, elem_value in VANILLA_PYTHON_VERSIONS.items():
230+
self.python_version_select.addItem(elem_key, elem_value)
231+
232+
# Set the default selection to the last Python version
233+
VANILLA_PYTHON_VERSIONS
234+
default_index = self.python_version_select.findText(
235+
list(VANILLA_PYTHON_VERSIONS.keys())[-1]
236+
)
235237
self.python_version_select.setCurrentIndex(default_index)
236238
python_version_layout.addWidget(self.python_version_select)
237239

@@ -561,14 +563,13 @@ def download_and_install(self):
561563
filename = f"python-{selected_version}-amd64.exe"
562564
LOG.info("Installing vanilla Python %s", selected_version)
563565
else:
564-
conda_version = "23.1.0-4"
565566
# OS based file download
566567
if is_linux_os():
567568
LOG.info("Linux")
568-
url, filename = get_conda_url_and_filename(conda_version)
569+
url, filename = get_conda_url_and_filename(CONDA_PYTHON_VERSION)
569570
else:
570-
url = f"https://github.com/conda-forge/miniforge/releases/download/{conda_version}/Miniforge3-{conda_version}-Windows-x86_64.exe"
571-
filename = f"Miniforge3-{conda_version}-Windows-x86_64.exe"
571+
url = f"https://github.com/conda-forge/miniforge/releases/download/{CONDA_PYTHON_VERSION}/Miniforge3-{CONDA_PYTHON_VERSION}-Windows-x86_64.exe"
572+
filename = f"Miniforge3-{CONDA_PYTHON_VERSION}-Windows-x86_64.exe"
572573
LOG.info("Installing miniconda from %s", url)
573574
try:
574575
self._download(url, filename, when_finished=self._run_install_python)

0 commit comments

Comments
 (0)