The graphical user interface (GUI) components are licensed under LGPL v3.0.
40 |
Except for the GUI components, your use of this software is governed by the MIT License. In addition, this installer allows you to access and install software that is licensed under separate terms ("Separately Licensed Software"). If you choose to install such Separately Licensed Software, you acknowledge that you are responsible for complying with any associated terms and conditions.
41 |
Copyright 2023 - 2024 ANSYS, Inc. All rights reserved.
Ansys Python Installer cannot verify whether it is up-to-date or not. This might be due to a permissions issue.
48 |
Currently installed version is {__version__}.
49 |
To check for the latest released version, visit the latest release site.
50 | """
51 |
52 | PYANSYS_DOCS_TEXT = f"""
PyAnsys Documentation
53 |
Access the documentation for the different PyAnsys projects by selecting your desired project and clicking on the 'Open Website' button.
54 |
Users are then redirected to the documentation websites for each of the projects.
55 |
Feel free to explore the different PyAnsys initiatives.
56 | """
57 |
58 | USER_PATH = os.path.expanduser("~")
59 | ANSYS_LINUX_PATH = f".local/ansys"
60 | ANSYS_FULL_LINUX_PATH = f"{USER_PATH}/{ANSYS_LINUX_PATH}"
61 |
62 | ANSYS_VENVS = ".ansys_python_venvs"
63 |
64 | ANSYS_SUPPORTED_PYTHON_VERSIONS = ["3_7", "3_10"]
65 |
66 | INSTALL_TEXT = """Choose to use either the standard Python install from python.org or miniforge."""
67 |
68 | PYTHON_VERSION_TEXT = """Choose the version of Python to install.
69 |
70 | While choosing the latest version of Python is generally recommended, some third-party libraries and applications may not yet be fully compatible with the newest release. Therefore, it is recommended to try the second newest version, as it will still have most of the latest features and improvements while also having broader support among third-party packages."""
71 |
72 | PRE_COMPILED_PYTHON_WARNING = """
73 | NOTE: Only 'Python 3.11' version is readily available. Other Python versions are compiled from source and it takes approximately 2-3 minutes."""
74 |
75 | PYTHON_VERSION_SELECTION_FOR_VENV = """Choose the version of Python to use for your virtual environment.
76 |
77 | Please select the Python version from the table below to create its respective virtual environment."""
78 |
79 | NAME_FOR_VENV = f"""Provide the name for your virtual environment.
80 |
81 |
Virtual environments are created under user directory /{ANSYS_LINUX_PATH + "/" + ANSYS_VENVS if is_linux() else ANSYS_VENVS} by default. To configure the default path, go to File >> Configure (Ctrl + D) and provide your preferred path.
82 |
83 | If the name provided already exists for another virtual environment, it will not be created. Users will receive a warning informing of the situation. For more details, refer here."""
84 |
85 | SELECT_VENV_MANAGE_TAB = f"""Choose a virtual environment to manage.
86 |
87 | It is recommended to use virtual environments for package management and launching options. Environments which are available under the user directory /{ANSYS_LINUX_PATH + "/" + ANSYS_VENVS if is_linux() else ANSYS_VENVS} are listed by default. To configure this default directory, refer here."""
88 |
89 | if getattr(sys, "frozen", False):
90 | # If the application is run as a bundle, the PyInstaller bootloader
91 | # extends the sys module by a flag frozen=True and sets the app
92 | # path into variable _MEIPASS'.
93 | try:
94 | THIS_PATH = sys._MEIPASS
95 | except:
96 | # this might occur on a single file install
97 | os.path.dirname(sys.executable)
98 | else:
99 | THIS_PATH = os.path.dirname(os.path.abspath(__file__))
100 |
101 |
102 | ASSETS_PATH = os.path.join(THIS_PATH, "assets")
103 |
104 | ANSYS_FAVICON = os.path.join(ASSETS_PATH, "ansys-favicon.png")
105 |
106 | PYANSYS_DOCS_SITES = {
107 | "PyAnsys": "https://docs.pyansys.com",
108 | "PyAnsys Developer docs": "https://dev.docs.pyansys.com",
109 | "PyACP": "https://acp.docs.pyansys.com",
110 | "PyAdditive": "https://additive.docs.pyansys.com",
111 | "PyAdditive Widgets": "https://widgets.additive.docs.pyansys.com",
112 | "PyAEDT": "https://aedt.docs.pyansys.com",
113 | "PyAnsys Geometry": "https://geometry.docs.pyansys.com",
114 | "PyAnsys Math": "https://math.docs.pyansys.com",
115 | "PyAnsys Sound": "https://sound.docs.pyansys.com",
116 | "PyConceptEV": "https://conceptev.docs.pyansys.com",
117 | "PyDPF - Core": "https://dpf.docs.pyansys.com",
118 | "PyDPF - Post": "https://post.docs.pyansys.com",
119 | "PyDPF - Composites": "https://composites.dpf.docs.pyansys.com",
120 | "PyDyna": "https://dyna.docs.pyansys.com",
121 | "PyDynamicReporting": "https://dynamicreporting.docs.pyansys.com",
122 | "PyEDB": "https://edb.docs.pyansys.com",
123 | "PyEDB - Core": "https://edb.core.docs.pyansys.com",
124 | "PyEnSight": "https://ensight.docs.pyansys.com",
125 | "PyFluent": "https://fluent.docs.pyansys.com",
126 | "PyFluent - Visualization": "https://visualization.fluent.docs.pyansys.com",
127 | "PyGranta": "https://grantami.docs.pyansys.com",
128 | "PyHPS": "https://hps.docs.pyansys.com",
129 | "PyMAPDL": "https://mapdl.docs.pyansys.com",
130 | "PyMAPDL Reader": "https://reader.docs.pyansys.com",
131 | "PyMechanical": "https://mechanical.docs.pyansys.com",
132 | "PyModelCenter": "https://modelcenter.docs.pyansys.com",
133 | "PyMotorCAD": "https://motorcad.docs.pyansys.com",
134 | "PyOptislang": "https://optislang.docs.pyansys.com",
135 | "PyPIM": "https://pypim.docs.pyansys.com",
136 | "PyPrimeMesh": "https://prime.docs.pyansys.com",
137 | "PyRocky": "https://rocky.docs.pyansys.com",
138 | "PySeascape": "https://seascape.docs.pyansys.com",
139 | "PySherlock": "https://sherlock.docs.pyansys.com",
140 | "PySimAI": "https://simai.docs.pyansys.com",
141 | "PySystemCoupling": "https://systemcoupling.docs.pyansys.com",
142 | "PyTurboGrid": "https://turbogrid.docs.pyansys.com",
143 | "PyTwin": "https://twin.docs.pyansys.com",
144 | "PyWorkbench": "https://workbench.docs.pyansys.com",
145 | # TOOLS
146 | "Ansys FileTransfer Tool": "https://filetransfer.tools.docs.pyansys.com",
147 | "Ansys Local Product Launcher": "https://local-product-launcher.tools.docs.pyansys.com",
148 | "Ansys Tools Path": "https://path.tools.docs.pyansys.com",
149 | "Ansys Tools Protobuf Compilation Helper": "https://ansys.github.io/ansys-tools-protoc-helper",
150 | "Ansys Tools Visualization Interface": "https://visualization-interface.tools.docs.pyansys.com",
151 | "PyAnsys Tools Report": "https://report.tools.docs.pyansys.com",
152 | "PyAnsys Tools Variable Interop": "https://variableinterop.docs.pyansys.com",
153 | "PyAnsys Tools Versioning": "https://versioning.tools.docs.pyansys.com",
154 | "PyAnsys Units": "http://units.docs.pyansys.com",
155 | "PyMaterials Manager": "https://manager.materials.docs.pyansys.com",
156 | }
157 |
158 | PYANSYS_LIBS = {
159 | "PyAnsys-Metapackage": "pyansys",
160 | "PyACP": "ansys-acp-core",
161 | "PyAdditive": "ansys-additive-core",
162 | "PyAdditive Widgets": "ansys-additive-widgets",
163 | "PyAEDT": "pyaedt",
164 | "PyAnsys Geometry": "ansys-geometry-core",
165 | "PyAnsys Math": "ansys-math-core",
166 | "PyAnsys Sound": "ansys-sound-core",
167 | "PyConceptEV": "ansys-conceptev-core",
168 | "PyDPF - Core": "ansys-dpf-core",
169 | "PyDPF - Post": "ansys-dpf-post",
170 | "PyDPF - Composites": "ansys-dpf-composites",
171 | "PyDyna": "ansys-dyna-core",
172 | "PyDynamicReporting": "ansys-dynamicreporting-core",
173 | "PyEDB": "pyedb",
174 | "PyEDB - Core": "ansys-edb-core",
175 | "PyEnSight": "ansys-pyensight-core",
176 | "PyFluent": "ansys-fluent-core",
177 | "PyFluent - Visualization": "ansys-fluent-visualization",
178 | "PyGranta": "pygranta",
179 | "PyHPS": "ansys-hps-client",
180 | "PyMAPDL": "ansys-mapdl-core",
181 | "PyMAPDL Reader": "ansys-mapdl-reader",
182 | "PyMechanical": "ansys-mechanical-core",
183 | "PyModelCenter": "ansys-modelcenter-workflow",
184 | "PyMotorCAD": "ansys-motorcad-core",
185 | "PyOptislang": "ansys-optislang-core",
186 | "PyPIM": "ansys-platform-instancemanagement",
187 | "PyPrimeMesh": "ansys-meshing-prime",
188 | "PyRocky": "ansys-rocky-core",
189 | "PySeascape": "ansys-seascape",
190 | "PySherlock": "ansys-sherlock-core",
191 | "PySimAI": "ansys-simai-core",
192 | "PySystemCoupling": "ansys-systemcoupling-core",
193 | "PyTurboGrid": "ansys-turbogrid-core",
194 | "PyTwin": "pytwin",
195 | "PyWorkbench": "ansys-workbench-core",
196 | "Granta MI BoM Analytics": "ansys-grantami-bomanalytics",
197 | "Granta MI RecordLists": "ansys-grantami-recordlists",
198 | "Shared Components": "ansys-openapi-common",
199 | # TOOLS
200 | "Ansys FileTransfer Tool": "ansys-tools-filetransfer",
201 | "Ansys Local Product Launcher": "ansys-tools-local-product-launcher",
202 | "Ansys Tools Path": "ansys-tools-path",
203 | "Ansys Tools Protobuf Compilation Helper": "ansys-tools-protoc-helper",
204 | "Ansys Tools Visualization Interface": "ansys-tools-visualization-interface",
205 | "PyAnsys Tools Report": "pyansys-tools-report",
206 | "PyAnsys Tools Variable Interop": "pyansys-tools-variableinterop",
207 | "PyAnsys Tools Versioning": "pyansys-tools-versioning",
208 | "PyAnsys Units": "ansys-units",
209 | "PyMaterials Manager": "ansys-materials-manager",
210 | }
211 |
212 | VENV_DEFAULT_PATH = "venv_default_path"
213 | VENV_SEARCH_PATH = "venv_search_path"
214 |
215 |
216 | ###############################################################################
217 | # Python versions
218 | ###############################################################################
219 | #
220 | # Do not modify below this section
221 | #
222 |
223 | VANILLA_PYTHON_VERSIONS = {
224 | "Python 3.10": "3.10.11",
225 | "Python 3.11": "3.11.9",
226 | "Python 3.12": "3.12.10",
227 | }
228 |
229 | CONDA_PYTHON_VERSION = "24.1.2-0"
230 |
--------------------------------------------------------------------------------
/src/ansys/tools/installer/create_virtual_environment.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | """Installed Python versions table module for Ansys Python Manager."""
24 |
25 | import logging
26 | import os
27 | from pathlib import Path
28 |
29 | from PySide6 import QtCore, QtGui, QtWidgets
30 |
31 | from ansys.tools.installer.configure_json import ConfigureJson
32 | from ansys.tools.installer.constants import (
33 | ANSYS_FAVICON,
34 | NAME_FOR_VENV,
35 | PYTHON_VERSION_SELECTION_FOR_VENV,
36 | )
37 | from ansys.tools.installer.installed_table import DataTable
38 | from ansys.tools.installer.linux_functions import (
39 | create_venv_linux,
40 | create_venv_linux_conda,
41 | is_linux_os,
42 | )
43 | from ansys.tools.installer.windows_functions import (
44 | create_venv_windows,
45 | create_venv_windows_conda,
46 | )
47 |
48 | ALLOWED_FOCUS_EVENTS = [QtCore.QEvent.Type.WindowActivate, QtCore.QEvent.Type.Show]
49 |
50 | LOG = logging.getLogger(__name__)
51 | LOG.setLevel("DEBUG")
52 |
53 |
54 | class CreateVenvTab(QtWidgets.QWidget):
55 | """Manage Virtual Environment w.r.t Python versions tab."""
56 |
57 | def __init__(self, parent):
58 | """Initialize this tab."""
59 | super().__init__()
60 | self._parent = parent
61 | self.venv_table = parent.installed_table_tab.venv_table
62 | layout = QtWidgets.QVBoxLayout()
63 | self.setLayout(layout)
64 |
65 | # QIcon object from an image file
66 | self.app_icon = QtGui.QIcon(ANSYS_FAVICON)
67 |
68 | # Group 1: Select Python version for virtual environment
69 | python_version_box = QtWidgets.QGroupBox("Select Python version")
70 | python_version_box_layout = QtWidgets.QVBoxLayout()
71 | python_version_box_layout.setContentsMargins(10, 20, 10, 20)
72 | python_version_box.setLayout(python_version_box_layout)
73 |
74 | # ---> Add text for selecting Python version
75 | python_version_box_text = QtWidgets.QLabel()
76 | python_version_box_text.setText(PYTHON_VERSION_SELECTION_FOR_VENV)
77 | python_version_box_text.setAlignment(QtCore.Qt.AlignmentFlag.AlignJustify)
78 | python_version_box_text.setWordWrap(True)
79 | python_version_box_layout.addWidget(python_version_box_text)
80 |
81 | # ---> Include table with available Python installations
82 | self.table = DataTable(installed_python=True, installed_forge=True)
83 | self.table.setSelectionMode(QtWidgets.QTableWidget.SingleSelection)
84 | python_version_box_layout.addWidget(self.table)
85 |
86 | # Group 2: Provide name for virtual environment
87 | venv_name_box = QtWidgets.QGroupBox("Virtual environment name")
88 | venv_name_box_layout = QtWidgets.QVBoxLayout()
89 | venv_name_box_layout.setContentsMargins(10, 20, 10, 20)
90 | venv_name_box.setLayout(venv_name_box_layout)
91 |
92 | # ---> Add text for virtual environment name
93 | venv_name_box_text = QtWidgets.QLabel()
94 | venv_name_box_text.setText(NAME_FOR_VENV)
95 | venv_name_box_text.setTextFormat(QtCore.Qt.TextFormat.RichText)
96 | venv_name_box_text.setAlignment(QtCore.Qt.AlignmentFlag.AlignJustify)
97 | venv_name_box_text.setOpenExternalLinks(True)
98 | venv_name_box_text.setWordWrap(True)
99 | venv_name_box_layout.addWidget(venv_name_box_text)
100 |
101 | # ---> Add box for virtual environment name insertion
102 | self.venv_name = QtWidgets.QLineEdit()
103 | self.venv_name.setPlaceholderText("Enter virtual environment name")
104 | venv_name_box_layout.addWidget(self.venv_name)
105 |
106 | # END: Create virtual environment button
107 | create_env_btn = QtWidgets.QPushButton("Create")
108 | create_env_btn.clicked.connect(self.create_venv)
109 |
110 | # Finally, add all the previous widgets to the global layout
111 | layout.addWidget(python_version_box)
112 | layout.addWidget(venv_name_box)
113 | layout.addWidget(create_env_btn)
114 |
115 | # And ensure the table is always in focus
116 | self.installEventFilter(self)
117 |
118 | def create_venv(self):
119 | """Create virtual environment at selected directory."""
120 | configure_json = ConfigureJson()
121 | create_venv_path = configure_json.default_path
122 | venv_dir = os.path.join(create_venv_path, self.venv_name.text())
123 | if self.venv_name.text() == "":
124 | self.failed_to_create_dialog(case_1=True)
125 | elif os.path.exists(venv_dir):
126 | self.failed_to_create_dialog(case_2=True)
127 | else:
128 | Path(venv_dir).mkdir(parents=True, exist_ok=True)
129 | try:
130 | self.cmd_create_venv(venv_dir)
131 | except:
132 | self.failed_to_create_dialog()
133 |
134 | self.update_table()
135 | self.venv_success_dialog()
136 |
137 | def venv_success_dialog(self):
138 | """Dialog appear for successful creation of virtual environment."""
139 | msg = QtWidgets.QMessageBox()
140 | msg.setText("Information: Virtual environment successfully created!")
141 | msg.setWindowTitle("Information")
142 | msg.setIcon(msg.Icon.Information)
143 | msg.setWindowIcon(self.app_icon)
144 | msg.exec_()
145 |
146 | def failed_to_create_dialog(self, case_1=False, case_2=False):
147 | """Dialogs for if environment gets failed to create."""
148 | if case_1:
149 | # Case 1: check for name of environment
150 | msg = QtWidgets.QMessageBox()
151 | msg.setText("Warning: Failed to create virtual environment!")
152 | msg.setInformativeText(
153 | "Enter a valid name for virtual environment creation."
154 | )
155 | msg.setWindowTitle("Warning")
156 | msg.setIcon(msg.Icon.Warning)
157 | msg.setWindowIcon(self.app_icon)
158 | msg.exec_()
159 |
160 | elif case_2:
161 | # Case 2: if environment already exists
162 | msg = QtWidgets.QMessageBox()
163 | msg.setText("Warning: Failed to create virtual environment!")
164 | msg.setInformativeText(
165 | "Virtual environment already exists. Please enter a different virtual environment name."
166 | )
167 | msg.setWindowTitle("Warning")
168 | msg.setIcon(msg.Icon.Warning)
169 | msg.setWindowIcon(self.app_icon)
170 | msg.exec_()
171 | else:
172 | # In case of critical error
173 | msg = QtWidgets.QMessageBox()
174 | msg.setText("Error: Failed to create virtual environment!")
175 | msg.setInformativeText("There might be some issue with application.")
176 | msg.setWindowTitle("Error")
177 | msg.setIcon(msg.Icon.Critical)
178 | msg.setWindowIcon(self.app_icon)
179 | msg.exec_()
180 |
181 | def update_table(self):
182 | """Update the Python version table."""
183 | self.table.update()
184 | self.venv_table.update()
185 |
186 | def eventFilter(self, source, event):
187 | """Filter events and ensure that the table always remains in focus."""
188 | if event.type() in ALLOWED_FOCUS_EVENTS and source is self:
189 | self.table.setFocus()
190 | return super().eventFilter(source, event)
191 |
192 | def cmd_create_venv(self, venv_dir):
193 | """Create a virtual environment in a new command prompt.
194 |
195 | Parameters
196 | ----------
197 | venv_dir : str
198 | The location for the virtual environment.
199 | """
200 | # Get the selected Python environment
201 | py_path = self.table.active_path
202 |
203 | LOG.debug(f"Requesting creation of {venv_dir}")
204 | if "Python" in self.table.active_version:
205 | if is_linux_os():
206 | create_venv_linux(venv_dir, py_path)
207 | else:
208 | create_venv_windows(venv_dir, py_path)
209 | else:
210 | if is_linux_os():
211 | create_venv_linux_conda(venv_dir, py_path)
212 | else:
213 | create_venv_windows_conda(venv_dir, py_path)
214 |
--------------------------------------------------------------------------------
/src/ansys/tools/installer/find_python.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | """Search for Python or miniforge installations within the Windows registry."""
24 |
25 | import logging
26 | import os
27 | import subprocess
28 |
29 | from ansys.tools.path import get_available_ansys_installations
30 |
31 | from ansys.tools.installer.configure_json import ConfigureJson
32 | from ansys.tools.installer.constants import ANSYS_SUPPORTED_PYTHON_VERSIONS
33 | from ansys.tools.installer.linux_functions import (
34 | find_ansys_installed_python_linux,
35 | find_miniforge_linux,
36 | is_linux_os,
37 | )
38 |
39 | # only used on windows
40 | try:
41 | import winreg
42 | except ModuleNotFoundError:
43 | pass
44 |
45 |
46 | LOG = logging.getLogger(__name__)
47 | LOG.setLevel("DEBUG")
48 |
49 |
50 | def find_miniforge():
51 | """Find all installations of miniforge within the Windows registry.
52 |
53 | Returns
54 | -------
55 | dict
56 | Dictionary containing a key for each path and a ``tuple``
57 | containing ``(version_str, is_admin)``.
58 |
59 | """
60 | if os.name == "nt":
61 | paths = _find_miniforge_win(True)
62 | paths.update(_find_miniforge_win(False))
63 | else:
64 | paths = find_miniforge_linux()
65 | return paths
66 |
67 |
68 | def _find_miniforge_win(admin=False):
69 | """Search for any miniforge installations in the registry."""
70 | if admin:
71 | root_key = winreg.HKEY_LOCAL_MACHINE
72 | else:
73 | root_key = winreg.HKEY_CURRENT_USER
74 |
75 | paths = {}
76 | key = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
77 | try:
78 | with winreg.OpenKey(
79 | root_key,
80 | key,
81 | access=winreg.KEY_READ,
82 | ) as reg_key:
83 | info = winreg.QueryInfoKey(reg_key)
84 | for i in range(info[0]):
85 | subkey_name = winreg.EnumKey(reg_key, i)
86 | if "Miniforge" in subkey_name:
87 | with winreg.OpenKey(
88 | root_key,
89 | key + "\\" + subkey_name,
90 | access=winreg.KEY_READ,
91 | ) as sub_key:
92 | ver = winreg.QueryValueEx(sub_key, "DisplayVersion")[0]
93 | uninstall_exe = winreg.QueryValueEx(sub_key, "UninstallString")[
94 | 0
95 | ].replace('"', "")
96 | miniforge_path = os.path.dirname(uninstall_exe)
97 | paths[miniforge_path] = (ver, admin)
98 | except FileNotFoundError:
99 | pass
100 |
101 | return paths
102 |
103 |
104 | def _find_installed_python_win(admin=False):
105 | """Check the windows registry for any installed instances of Python."""
106 | if admin:
107 | root_key = winreg.HKEY_LOCAL_MACHINE
108 | else:
109 | root_key = winreg.HKEY_CURRENT_USER
110 |
111 | paths = {}
112 | try:
113 | base_key = "SOFTWARE\\Python\\PythonCore"
114 | with winreg.OpenKey(
115 | root_key,
116 | base_key,
117 | access=winreg.KEY_READ,
118 | ) as reg_key:
119 | info = winreg.QueryInfoKey(reg_key)
120 | for i in range(info[0]):
121 | name = winreg.EnumKey(reg_key, i)
122 | ver, path = _get_python_info_win(f"{base_key}\\{name}", root_key)
123 | if ver is not None and path is not None:
124 | paths[path] = (ver, admin)
125 |
126 | except FileNotFoundError:
127 | pass
128 |
129 | return paths
130 |
131 |
132 | def _find_installed_ansys_python_win():
133 | """Check the Ansys installation folder for installed Python."""
134 | installed_ansys = get_available_ansys_installations()
135 | paths = {}
136 | for ver in installed_ansys:
137 | ansys_path = installed_ansys[ver]
138 | for ansys_py_ver in ANSYS_SUPPORTED_PYTHON_VERSIONS:
139 | path = os.path.join(
140 | ansys_path,
141 | f"commonfiles\\CPython\\{ansys_py_ver}\\winx64\\Release\\python",
142 | )
143 | if os.path.exists(path):
144 | try:
145 | version_output = subprocess.check_output(
146 | [f"{path}\\python.exe", "--version"], text=True
147 | ).strip()
148 | version = version_output.split()[1]
149 | paths[path] = (version, False)
150 | except Exception as err:
151 | LOG.error(f"Failed to retrieve Python version: {str(err)}")
152 | pass
153 |
154 | return paths
155 |
156 |
157 | def _find_installed_python_linux():
158 | """
159 | Find all installed Python versions on Linux.
160 |
161 | Returns
162 | -------
163 | dict
164 | Dictionary containing a key for each path and a tuple
165 | containing ``(version: str, admin: bool)``.
166 |
167 | Examples
168 | --------
169 | >>> installed_pythons = find_installed_python_linux()
170 | >>> installed_pythons
171 | {'/home/user/python/py311/bin/python': ('3.11.3', False),
172 | '/home/user/python/py311/bin/python3': ('3.11.3', False),
173 |
174 | """
175 | LOG.debug("Identifying all installed versions of python on Linux")
176 |
177 | pythons = {}
178 | version_names = ["python", "python3"] + [f"python3.{i}" for i in range(7, 13)]
179 | previous_found_version = ""
180 |
181 | for version_name in version_names:
182 | try:
183 | path = subprocess.check_output(["which", version_name], text=True).strip()
184 | version_output = subprocess.check_output(
185 | [path, "--version"], text=True
186 | ).strip()
187 | version = version_output.split()[1]
188 | admin = path.startswith("/usr")
189 | if version != previous_found_version:
190 | pythons[path] = (version, admin)
191 | LOG.debug("Identified %s at %s", version, path)
192 | previous_found_version = version
193 | except:
194 | # Ignore if the command fails (e.g., if the Python version is not installed)
195 | pass
196 |
197 | return pythons
198 |
199 |
200 | def _get_python_info_win(key, root_key):
201 | """For a given windows key, read the install path and python version."""
202 | with winreg.OpenKey(root_key, key, access=winreg.KEY_READ) as reg_key:
203 | try:
204 | ver = winreg.QueryValueEx(reg_key, "Version")[0]
205 | except FileNotFoundError:
206 | ver = None
207 |
208 | try:
209 | with winreg.OpenKey(
210 | root_key, f"{key}\\InstallPath", access=winreg.KEY_READ
211 | ) as path_key:
212 | path = winreg.QueryValueEx(path_key, None)[0]
213 | except FileNotFoundError:
214 | path = None
215 |
216 | return ver, path
217 |
218 |
219 | def find_all_python():
220 | """Find any installed instances of python.
221 |
222 | Returns
223 | -------
224 | dict
225 | Dictionary containing a key for each path and a ``tuple``
226 | containing ``(version_str, is_admin)``.
227 |
228 | """
229 | if os.name == "nt":
230 | paths = _find_installed_python_win(True)
231 | paths.update(_find_installed_python_win(False))
232 | paths.update(_find_installed_ansys_python_win())
233 | else:
234 | paths = _find_installed_python_linux()
235 | paths.update(find_ansys_installed_python_linux())
236 |
237 | return paths
238 |
239 |
240 | def get_all_python_venv():
241 | """Get a list of all created python virtual environments.
242 |
243 | Returns
244 | -------
245 | dict
246 | Dictionary containing a key for each path and a ``tuple``
247 | containing ``(version_str, is_admin)``.
248 | """
249 | paths = {}
250 | script_path = "bin" if is_linux_os() else "Scripts"
251 | configure = ConfigureJson()
252 | for venv_dir in configure.venv_search_path:
253 | try:
254 | for venv_dir_name in os.listdir(venv_dir):
255 | if os.path.isfile(
256 | os.path.join(venv_dir, venv_dir_name, script_path, "activate")
257 | ) or (
258 | not os.path.isdir(os.path.join(venv_dir, venv_dir_name, "condabin"))
259 | and os.path.isdir(
260 | os.path.join(venv_dir, venv_dir_name, "conda-meta")
261 | )
262 | ):
263 |
264 | path = os.path.join(venv_dir, venv_dir_name, script_path)
265 | paths[path] = (
266 | venv_dir_name,
267 | False,
268 | ) # venvs will always be user-like, hence False
269 | except:
270 | pass
271 | return paths
272 |
--------------------------------------------------------------------------------
/src/ansys/tools/installer/installer.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | """Installer module for Ansys Python Manager."""
24 |
25 | from ansys.tools.installer.linux_functions import install_python_linux, is_linux_os
26 | from ansys.tools.installer.windows_functions import install_python_windows
27 |
28 |
29 | def install_python(filename, wait=True):
30 | """Install "vanilla" python for a single user."""
31 | if is_linux_os():
32 | install_python_linux(filename)
33 | return "Success", None
34 | else:
35 | return install_python_windows(filename, wait)
36 |
--------------------------------------------------------------------------------
/src/ansys/tools/installer/misc.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | """Contains miscellaneous functionalities this library."""
24 |
25 | import logging
26 | import sys
27 |
28 | from PySide6 import QtCore, QtGui, QtWidgets
29 |
30 | from ansys.tools.installer.constants import (
31 | ANSYS_FAVICON,
32 | PYANSYS_DOCS_SITES,
33 | PYANSYS_DOCS_TEXT,
34 | )
35 |
36 |
37 | def enable_logging():
38 | """Log to stdout."""
39 |
40 | class SafeStreamHandler(logging.StreamHandler):
41 | def emit(self, record):
42 | try:
43 | if not self.stream.closed:
44 | super().emit(record)
45 | except (ValueError, AttributeError):
46 | pass
47 |
48 | logger = logging.getLogger()
49 | logger.setLevel(logging.DEBUG)
50 |
51 | # Create a console handler that writes to stdout
52 | console_handler = SafeStreamHandler(sys.stdout)
53 | console_handler.setLevel(logging.DEBUG)
54 | formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
55 | console_handler.setFormatter(formatter)
56 |
57 | # Add the console handler to the logger
58 | logger.addHandler(console_handler)
59 |
60 |
61 | class ImageWidget(QtWidgets.QLabel):
62 | """Automatic scaled image widget."""
63 |
64 | def __init__(self, parent=None):
65 | """Instantiate an ImageWidget."""
66 | super().__init__(parent)
67 | self.setScaledContents(True)
68 |
69 | def hasHeightForWidth(self):
70 | """Override height for width for autoscaling (when pixmap)."""
71 | return self.pixmap() is not None
72 |
73 | def heightForWidth(self, w):
74 | """Override height for width for autoscaling."""
75 | if self.pixmap():
76 | return int(w * (self.pixmap().height() / self.pixmap().width()))
77 |
78 |
79 | class PyAnsysDocsBox(QtWidgets.QMessageBox):
80 | """PyAnsys documentation message box."""
81 |
82 | def __init__(self, parent=None):
83 | """Instantiate a PyAnsysDocsBox."""
84 | super().__init__(parent)
85 | self.setWindowTitle("PyAnsys Documentation")
86 | pixmap = QtGui.QPixmap(ANSYS_FAVICON).scaledToHeight(
87 | 32, QtCore.Qt.SmoothTransformation
88 | )
89 | self.setWindowIcon(QtGui.QIcon(ANSYS_FAVICON))
90 | self.setIconPixmap(pixmap)
91 | self.setText(PYANSYS_DOCS_TEXT)
92 | self.setStandardButtons(QtWidgets.QMessageBox.StandardButton.NoButton)
93 |
94 | # create a combo box and add items
95 | self.comboBox = QtWidgets.QComboBox(self)
96 | for key, value in PYANSYS_DOCS_SITES.items():
97 | self.comboBox.addItem(key, value)
98 | sizePolicy = QtWidgets.QSizePolicy(
99 | QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
100 | )
101 | self.comboBox.setSizePolicy(sizePolicy)
102 |
103 | # create a button
104 | self.pushButton = QtWidgets.QPushButton("Open Website", self)
105 | self.pushButton.setSizePolicy(sizePolicy)
106 |
107 | # add the combo box and button to the message box
108 | layout = self.layout()
109 | layout.addWidget(self.comboBox, layout.rowCount(), 0, 1, layout.columnCount())
110 | layout.addWidget(self.pushButton, layout.rowCount(), 0, 1, layout.columnCount())
111 |
112 | # add an empty widget to the layout to hold the Close button
113 | spacer = QtWidgets.QWidget(self)
114 | spacer.setSizePolicy(sizePolicy)
115 | layout.addWidget(spacer, layout.rowCount(), 0, 1, layout.columnCount())
116 |
117 | # create the Close button and add it to the spacer widget
118 | self.closeButton = self.addButton(QtWidgets.QMessageBox.StandardButton.Close)
119 | layout.addWidget(
120 | self.closeButton, layout.rowCount(), 0, 1, layout.columnCount()
121 | )
122 |
123 | # Hide the close button (for now)
124 | self.closeButton.hide()
125 |
126 | # connect the button to a slot that opens the selected website
127 | self.pushButton.clicked.connect(self.open_website)
128 |
129 | def open_website(self):
130 | """Open the URL to the docs site chosen."""
131 | # get the selected index
132 | index = self.comboBox.currentIndex()
133 |
134 | # get the URL for the selected index
135 | url = self.comboBox.itemData(index)
136 |
137 | # open the corresponding website
138 | QtGui.QDesktopServices.openUrl(QtCore.QUrl(url))
139 |
--------------------------------------------------------------------------------
/src/ansys/tools/installer/progress_bar.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | """Progress bar module for Ansys Python Manager."""
24 |
25 | from PySide6 import QtCore, QtWidgets
26 |
27 |
28 | class ProgressBar(QtWidgets.QDialog):
29 | """ProgressBar class."""
30 |
31 | def __init__(self, parent, nticks, title="Progress Bar", label=None, show=True):
32 | """Instantiate a ProgressBar object."""
33 | super().__init__(parent)
34 | self._pbar = QtWidgets.QProgressBar(self)
35 | self._pbar.setValue(0)
36 | # self._pbar.setGeometry(30, 40, 500, 75)
37 | self.layout = QtWidgets.QVBoxLayout()
38 | if label:
39 | label_widget = QtWidgets.QLabel()
40 | label_widget.setText(label)
41 | label_widget.setSizePolicy(
42 | QtWidgets.QSizePolicy.Expanding,
43 | QtWidgets.QSizePolicy.Expanding,
44 | )
45 | label_widget.setAlignment(QtCore.Qt.AlignCenter)
46 | self.layout.addWidget(label_widget)
47 | self.layout.addWidget(self._pbar)
48 | self.setLayout(self.layout)
49 | self.setGeometry(300, 300, 550, 100)
50 | self.setWindowTitle(title)
51 | if show:
52 | self.show()
53 |
54 | # total number of values for QProgressBar is 100
55 | self._increment_value = 100 // nticks
56 |
57 | def increment(self):
58 | """Increments bar."""
59 | self._pbar.setValue(self._pbar.value() + self._increment_value)
60 |
61 | def set_value(self, value):
62 | """Set the value of the progress bar."""
63 | self._pbar.setValue(value)
64 |
65 | @property
66 | def value(self):
67 | """Return the value of the progress bar."""
68 | return self._pbar.value()
69 |
--------------------------------------------------------------------------------
/src/ansys/tools/installer/uninstall.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | """Uninstall application."""
24 | import os
25 | import shutil
26 |
27 | from PySide6 import QtCore, QtGui, QtWidgets
28 |
29 | from ansys.tools.installer.configure_json import ConfigureJson
30 | from ansys.tools.installer.constants import ANSYS_FAVICON, ASSETS_PATH
31 | from ansys.tools.installer.linux_functions import (
32 | execute_linux_command,
33 | find_ansys_installed_python_linux,
34 | find_miniforge_linux,
35 | get_os_version,
36 | is_linux_os,
37 | )
38 |
39 |
40 | class Uninstall(QtWidgets.QWidget):
41 | """Instantiate uninstall class."""
42 |
43 | def __init__(self, parent):
44 | """Initialize this tab."""
45 | try:
46 | super().__init__()
47 | self._parent = parent
48 | self._parent.uninstall_window = QtWidgets.QWidget()
49 | self._parent.uninstall_window.move(
50 | self._parent.uninstall_window.frameGeometry().center()
51 | )
52 | uninstall_window_label = QtWidgets.QLabel()
53 | uninstall_window_label.setText("Do you want to uninstall the application?")
54 | uninstall_window_label.setTextFormat(QtCore.Qt.TextFormat.RichText)
55 | uninstall_window_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignJustify)
56 | uninstall_window_label.setWordWrap(True)
57 |
58 | uninstall_options_layout = QtWidgets.QVBoxLayout()
59 |
60 | # Group 1: Configure Virtual Environment Create path
61 | uninstall_window_cache_remove = QtWidgets.QGroupBox("Remove:")
62 | uninstall_window_cache_remove_layout = QtWidgets.QVBoxLayout()
63 | uninstall_window_cache_remove_layout.setContentsMargins(10, 20, 10, 20)
64 | uninstall_window_cache_remove.setLayout(
65 | uninstall_window_cache_remove_layout
66 | )
67 |
68 | # venv
69 | uninstall_window_cache_remove_venv_layout = QtWidgets.QHBoxLayout()
70 |
71 | uninstall_window_cache_remove_venv_text = QtWidgets.QLabel()
72 | uninstall_window_cache_remove_venv_text.setText(
73 | "Delete virtual environments "
74 | )
75 | uninstall_window_cache_remove_venv_text.setTextFormat(
76 | QtCore.Qt.TextFormat.RichText
77 | )
78 | uninstall_window_cache_remove_venv_text.setAlignment(
79 | QtCore.Qt.AlignmentFlag.AlignLeft
80 | )
81 |
82 | self.uninstall_window_cache_remove_venv_checkbox = QtWidgets.QCheckBox()
83 |
84 | uninstall_window_cache_remove_venv_layout.addWidget(
85 | uninstall_window_cache_remove_venv_text
86 | )
87 | uninstall_window_cache_remove_venv_layout.addWidget(
88 | self.uninstall_window_cache_remove_venv_checkbox
89 | )
90 |
91 | # remove installed python
92 | uninstall_window_cache_remove_python_layout = QtWidgets.QHBoxLayout()
93 | uninstall_window_cache_remove_python_text = QtWidgets.QLabel()
94 | uninstall_window_cache_remove_python_text.setText(
95 | "Delete Python installations"
96 | )
97 | uninstall_window_cache_remove_python_text.setTextFormat(
98 | QtCore.Qt.TextFormat.RichText
99 | )
100 | uninstall_window_cache_remove_python_text.setAlignment(
101 | QtCore.Qt.AlignmentFlag.AlignLeft
102 | )
103 |
104 | self.uninstall_window_cache_remove_python_checkbox = QtWidgets.QCheckBox()
105 |
106 | uninstall_window_cache_remove_python_layout.addWidget(
107 | uninstall_window_cache_remove_python_text
108 | )
109 | uninstall_window_cache_remove_python_layout.addWidget(
110 | self.uninstall_window_cache_remove_python_checkbox
111 | )
112 |
113 | # configs
114 | uninstall_window_cache_remove_configs_layout = QtWidgets.QHBoxLayout()
115 |
116 | uninstall_window_cache_remove_configs_text = QtWidgets.QLabel()
117 | uninstall_window_cache_remove_configs_text.setText("Delete configurations ")
118 | uninstall_window_cache_remove_configs_text.setTextFormat(
119 | QtCore.Qt.TextFormat.RichText
120 | )
121 | uninstall_window_cache_remove_configs_text.setAlignment(
122 | QtCore.Qt.AlignmentFlag.AlignLeft
123 | )
124 | uninstall_window_cache_remove_configs_text.setWordWrap(True)
125 |
126 | self.uninstall_window_cache_remove_configs_checkbox = QtWidgets.QCheckBox()
127 |
128 | uninstall_window_cache_remove_configs_layout.addWidget(
129 | uninstall_window_cache_remove_configs_text
130 | )
131 | uninstall_window_cache_remove_configs_layout.addWidget(
132 | self.uninstall_window_cache_remove_configs_checkbox
133 | )
134 |
135 | # add layout to group
136 | uninstall_window_cache_remove_layout.addLayout(
137 | uninstall_window_cache_remove_venv_layout
138 | )
139 |
140 | uninstall_window_cache_remove_layout.addLayout(
141 | uninstall_window_cache_remove_python_layout
142 | )
143 |
144 | uninstall_window_cache_remove_layout.addLayout(
145 | uninstall_window_cache_remove_configs_layout
146 | )
147 |
148 | uninstall_options_layout.addWidget(uninstall_window_cache_remove)
149 |
150 | uninstall_window_button_save = QtWidgets.QPushButton("Uninstall")
151 | uninstall_window_button_save.clicked.connect(
152 | lambda x: self._pop_up(
153 | "Do you want to proceed uninstall?", self._uninstall
154 | )
155 | )
156 | uninstall_window_button_close = QtWidgets.QPushButton("Close")
157 | uninstall_window_button_close.clicked.connect(
158 | lambda x: self._pop_up("Do you want to close?", self._close_all)
159 | )
160 |
161 | uninstall_window_layout_2 = QtWidgets.QHBoxLayout()
162 | uninstall_window_layout_2.addWidget(uninstall_window_button_save)
163 | uninstall_window_layout_2.addWidget(uninstall_window_button_close)
164 |
165 | uninstall_window_layout = QtWidgets.QVBoxLayout()
166 | uninstall_window_layout.addLayout(uninstall_options_layout)
167 | uninstall_window_layout.addLayout(uninstall_window_layout_2)
168 | self._parent.uninstall_window.setLayout(uninstall_window_layout)
169 |
170 | self._parent.uninstall_window.setWindowTitle("Uninstall")
171 | self._parent.uninstall_window.setWindowIcon(QtGui.QIcon(ANSYS_FAVICON))
172 | self._parent.uninstall_window.setWindowFlag(
173 | QtCore.Qt.WindowCloseButtonHint, False
174 | )
175 | self._parent.uninstall_window.resize(500, 40)
176 | self._parent.uninstall_window.show()
177 |
178 | except Exception as e:
179 | self._parent.show_error(str(e))
180 |
181 | def _uninstall(self):
182 | """Uninstallation function. Execute the uninstaller script."""
183 | if self.uninstall_window_cache_remove_venv_checkbox.isChecked():
184 | self._remove_all_venvs()
185 |
186 | if self.uninstall_window_cache_remove_python_checkbox.isChecked():
187 | self._remove_all_installed_python()
188 |
189 | if self.uninstall_window_cache_remove_configs_checkbox.isChecked():
190 | self._remove_configs()
191 |
192 | os_version = get_os_version()
193 | if os_version in ["centos", "fedora"]:
194 | script_path = os.path.join(ASSETS_PATH, "uninstaller_yum.sh")
195 | execute_linux_command(f"{script_path}", wait=False)
196 | elif get_os_version().startswith("2"):
197 | script_path = os.path.join(ASSETS_PATH, "uninstaller_ubuntu.sh")
198 | execute_linux_command(f"{script_path}", wait=False)
199 |
200 | self.user_confirmation_form.close()
201 | self._parent.uninstall_window.close()
202 | self._parent.close_emit()
203 |
204 | def _remove_all_installed_python(self):
205 | for path in find_ansys_installed_python_linux():
206 | path = path.split("bin")[0]
207 | shutil.rmtree(path, ignore_errors=True)
208 | for path in find_miniforge_linux(ansys_manager_installed_only=True):
209 | shutil.rmtree(path, ignore_errors=True)
210 |
211 | def _remove_all_venvs(self):
212 | """Remove all the venv created by Ansys Python Manager."""
213 | try:
214 | configure = ConfigureJson()
215 | script_path = "bin" if is_linux_os() else "Scripts"
216 | for venv_dir in configure.history["path"]:
217 | for venv_dir_name in os.listdir(venv_dir):
218 | if os.path.isfile(
219 | os.path.join(venv_dir, venv_dir_name, script_path, "activate")
220 | ) or (
221 | not os.path.isdir(
222 | os.path.join(venv_dir, venv_dir_name, "condabin")
223 | )
224 | and os.path.isdir(
225 | os.path.join(venv_dir, venv_dir_name, "conda-meta")
226 | )
227 | ):
228 | print(f"removed {os.path.join(venv_dir, venv_dir_name)}")
229 | shutil.rmtree(
230 | os.path.join(venv_dir, venv_dir_name), ignore_errors=True
231 | )
232 | except:
233 | pass
234 |
235 | def _remove_configs(self):
236 | """Remove all the configurations created by Ansys Python Manager."""
237 | try:
238 | configure = ConfigureJson()
239 | print(f"removed {configure.config_dir}")
240 | shutil.rmtree(configure.config_dir, ignore_errors=True)
241 | except:
242 | pass
243 |
244 | def _close_all(self):
245 | """Close all the pop-up window."""
246 | self.user_confirmation_form.close()
247 | self._parent.uninstall_window.close()
248 |
249 | def _pop_up(self, message, call_back):
250 | """Launch the confirmation pop-up window."""
251 | self.user_confirmation_form = QtWidgets.QWidget()
252 | self.user_confirmation_form.move(
253 | self.user_confirmation_form.frameGeometry().center()
254 | )
255 | user_confirmation_label = QtWidgets.QLabel()
256 | user_confirmation_label.setText(message)
257 | user_confirmation_label.setOpenExternalLinks(True)
258 | user_confirmation_label.setTextFormat(QtCore.Qt.TextFormat.RichText)
259 | user_confirmation_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignJustify)
260 | user_confirmation_label.setWordWrap(True)
261 |
262 | user_confirmation_layout_horizontal = QtWidgets.QHBoxLayout()
263 | user_confirmation_yes_button = QtWidgets.QPushButton("Yes")
264 | user_confirmation_yes_button.clicked.connect(call_back)
265 | user_confirmation_no_button = QtWidgets.QPushButton("No")
266 | user_confirmation_no_button.clicked.connect(self.user_confirmation_form.close)
267 | user_confirmation_layout = QtWidgets.QVBoxLayout()
268 | user_confirmation_layout.addWidget(user_confirmation_label)
269 | user_confirmation_layout_horizontal.addWidget(user_confirmation_yes_button)
270 | user_confirmation_layout_horizontal.addWidget(user_confirmation_no_button)
271 | user_confirmation_layout.addLayout(user_confirmation_layout_horizontal)
272 | self.user_confirmation_form.setLayout(user_confirmation_layout)
273 | self.user_confirmation_form.setWindowTitle("Confirmation")
274 | icon = QtGui.QIcon(ANSYS_FAVICON)
275 | self.user_confirmation_form.setWindowIcon(icon)
276 | self.user_confirmation_form.resize(400, 40)
277 | self.user_confirmation_form.setWindowFlag(
278 | QtCore.Qt.WindowCloseButtonHint, False
279 | )
280 | self.user_confirmation_form.show()
281 |
--------------------------------------------------------------------------------
/src/ansys/tools/installer/vscode.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | """VS code launch window."""
24 | import os
25 |
26 | from PySide6 import QtCore, QtGui, QtWidgets
27 |
28 | from ansys.tools.installer.constants import ANSYS_FAVICON, USER_PATH
29 |
30 |
31 | class VSCode(QtWidgets.QWidget):
32 | """VS code launch window."""
33 |
34 | def __init__(self, parent):
35 | """Initialize this class."""
36 | try:
37 | super().__init__()
38 | self._parent = parent
39 | if self.is_vscode_installed():
40 | self._parent.vscode_window = QtWidgets.QWidget()
41 | self._parent.vscode_window.move(
42 | self._parent.vscode_window.frameGeometry().center()
43 | )
44 | vscode_window_label = QtWidgets.QLabel()
45 | vscode_window_label.setText("Configuration")
46 | vscode_window_label.setTextFormat(QtCore.Qt.TextFormat.RichText)
47 | vscode_window_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignJustify)
48 | vscode_window_label.setWordWrap(True)
49 |
50 | vscode_layout = QtWidgets.QVBoxLayout()
51 |
52 | # Group 1: Configure default Virtual Environment creation path
53 | vscode_window_path_config = QtWidgets.QGroupBox(
54 | "VS Code open directory:"
55 | )
56 | vscode_window_path_config_layout = QtWidgets.QVBoxLayout()
57 | vscode_window_path_config_layout.setContentsMargins(10, 20, 10, 20)
58 | vscode_window_path_config.setLayout(vscode_window_path_config_layout)
59 |
60 | # ---> Add box
61 | self.vscode_window_path_config_edit = QtWidgets.QLineEdit()
62 | self.vscode_window_path_config_edit.setText(USER_PATH)
63 | vscode_window_path_config_layout.addWidget(
64 | self.vscode_window_path_config_edit
65 | )
66 |
67 | self.vscode_warning_text = QtWidgets.QLabel()
68 | self.vscode_warning_text.setAlignment(
69 | QtCore.Qt.AlignmentFlag.AlignJustify
70 | )
71 | self.vscode_warning_text.setWordWrap(True)
72 | vscode_window_path_config_layout.addWidget(self.vscode_warning_text)
73 |
74 | # Finally, add all the previous widgets to the global layout
75 | vscode_layout.addWidget(vscode_window_path_config)
76 |
77 | vscode_window_button_open = QtWidgets.QPushButton("Open")
78 | vscode_window_button_open.clicked.connect(lambda x: self._open_vscode())
79 | vscode_window_button_close = QtWidgets.QPushButton("Close")
80 | vscode_window_button_close.clicked.connect(lambda x: self._close_all())
81 |
82 | vscode_window_layout_1 = QtWidgets.QHBoxLayout()
83 | vscode_window_layout_1.addWidget(vscode_window_label)
84 | vscode_window_layout_2 = QtWidgets.QHBoxLayout()
85 | vscode_window_layout_2.addWidget(vscode_window_button_open)
86 | vscode_window_layout_2.addWidget(vscode_window_button_close)
87 |
88 | vscode_window_layout = QtWidgets.QVBoxLayout()
89 | vscode_window_layout.addLayout(vscode_window_layout_1)
90 | vscode_window_layout.addLayout(vscode_layout)
91 | vscode_window_layout.addLayout(vscode_window_layout_2)
92 | self._parent.vscode_window.setLayout(vscode_window_layout)
93 |
94 | self._parent.vscode_window.setWindowTitle("Configuration")
95 | self._parent.vscode_window.setWindowIcon(QtGui.QIcon(ANSYS_FAVICON))
96 | self._parent.vscode_window.resize(500, 40)
97 | self._parent.vscode_window.show()
98 | else:
99 | msg = QtWidgets.QMessageBox()
100 | msg.setTextFormat(QtCore.Qt.TextFormat.RichText)
101 | msg.warning(
102 | self,
103 | "VS Code Launch Error",
104 | f"Failed to launch vscode. Try reinstalling code by following this link",
105 | )
106 |
107 | except Exception as e:
108 | self._parent.show_error(str(e))
109 |
110 | def _open_vscode(self):
111 | """Open VS code from path."""
112 | # handle errors
113 | path = self.vscode_window_path_config_edit.text().strip()
114 | if os.path.exists(rf"{path}"):
115 | error_msg = "echo Failed to launch vscode. Try reinstalling code by following this link https://code.visualstudio.com/download"
116 | self._parent.launch_cmd(f'code "{path}" && exit 0 || {error_msg}')
117 | self._parent.vscode_window.close()
118 | else:
119 | self.vscode_warning_text.setText(
120 | f"""{path} does not exist. Provide a valid path."""
121 | )
122 | self.vscode_warning_text.setStyleSheet(
123 | """
124 | color: rgb(255, 0, 0);
125 | """
126 | )
127 |
128 | def _close_all(self):
129 | """Close all the pop-up window."""
130 | self._parent.vscode_window.close()
131 |
132 | def is_vscode_installed(self):
133 | """Check if VSCode is installed or not.
134 |
135 | Returns
136 | -------
137 | bool
138 | Whether VSCode is installed or not.
139 | """
140 | try:
141 | return_val = os.system("code --version")
142 | if return_val == 0:
143 | return True
144 | else:
145 | return False
146 | except:
147 | return False
148 |
--------------------------------------------------------------------------------
/src/ansys/tools/installer/windows_functions.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | """Windows functions."""
24 |
25 | import logging
26 | import os
27 | import subprocess
28 |
29 | LOG = logging.getLogger(__name__)
30 |
31 |
32 | def create_venv_windows(venv_dir: str, py_path: str):
33 | r"""
34 | Create a virtual environment on Windows.
35 |
36 | Parameters
37 | ----------
38 | venv_dir : str
39 | Name of the virtual environment.
40 | py_path : str
41 | Path to the Python executable.
42 |
43 | Examples
44 | --------
45 | >>> create_venv_windows("my_venv", "C:\\Python39\\python.exe")
46 |
47 | """
48 | user_profile = os.path.expanduser("~")
49 |
50 | # Update the package managers
51 | try:
52 | # Update pip and uv using the py_path
53 | LOG.debug("Updating package managers - pip & uv...")
54 | subprocess.call(
55 | f'start /w /min cmd /K ""{py_path}\\python.exe" -m pip install --upgrade pip uv && exit"',
56 | shell=True,
57 | cwd=user_profile,
58 | )
59 |
60 | # Create venv using uv
61 | LOG.debug("Creating virtual environment using uv...")
62 | subprocess.call(
63 | f'start /w /min cmd /K ""{py_path}\\python.exe" -m uv venv --seed {venv_dir} && exit"',
64 | shell=True,
65 | cwd=user_profile,
66 | )
67 |
68 | # Check & Update default venv packages
69 | LOG.debug("Updating virtual environment packages...")
70 | venv_python = os.path.join(venv_dir, "Scripts", "python.exe")
71 | subprocess.call(
72 | f'start /w /min cmd /K "{venv_python} -m pip install --upgrade pip uv && exit"',
73 | shell=True,
74 | cwd=user_profile,
75 | )
76 | except Exception as e:
77 | LOG.debug(f"Error creating virtual environment: {e}")
78 |
79 |
80 | def create_venv_windows_conda(venv_dir: str, py_path: str):
81 | r"""
82 | Create a virtual environment on Windows using conda.
83 |
84 | Parameters
85 | ----------
86 | venv_dir : str
87 | Name of the virtual environment.
88 | py_path : str
89 | Path to the Python executable.
90 |
91 | Examples
92 | --------
93 | >>> create_venv_windows_conda("my_venv", "C:\\Python39\\python.exe")
94 |
95 | """
96 | user_profile = os.path.expanduser("~")
97 | subprocess.call(
98 | f'start /w /min cmd /K "{py_path}\\Scripts\\activate.bat && conda create --prefix {venv_dir} python -y && exit"',
99 | shell=True,
100 | cwd=user_profile,
101 | )
102 |
103 |
104 | def run_ps(command, full_path_to_ps=False):
105 | """Run a powershell command as admin."""
106 | ps = (
107 | r"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
108 | if full_path_to_ps
109 | else "powershell.exe"
110 | )
111 | ps_command = [ps, command]
112 |
113 | LOG.debug("Running: %s", str(ps_command))
114 | proc = subprocess.run(
115 | ps_command,
116 | stdout=subprocess.PIPE,
117 | stderr=subprocess.STDOUT,
118 | shell=True,
119 | )
120 |
121 | # stderr goes through stdout too
122 | if proc.returncode: # If return code != 0
123 | LOG.error("From powershell: %s", proc.stdout)
124 | if full_path_to_ps == False:
125 | return run_ps(command, full_path_to_ps=True)
126 |
127 | else:
128 | LOG.debug("From powershell: %s", proc.stdout)
129 |
130 | return proc.stdout.decode(), proc.returncode
131 |
132 |
133 | def install_python_windows(filename: str, wait: bool) -> tuple[str, int]:
134 | """Install "vanilla" python for a single user.
135 |
136 | Parameters
137 | ----------
138 | filename : str
139 | Path to the Python installer.
140 | wait : bool
141 | Wait for the installation to complete.
142 |
143 | Returns
144 | -------
145 | str
146 | Output from the installation.
147 | int
148 | Return code from the installation.
149 | """
150 | wait_str = " -Wait" if wait else ""
151 | command = f"(Start-Process '{filename}' -ArgumentList '/passive InstallAllUsers=0' {wait_str})"
152 | return run_ps(command)
153 |
--------------------------------------------------------------------------------
/tests/test_auto_updater.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | import os
24 |
25 | from packaging.version import Version
26 |
27 | from ansys.tools.installer.auto_updater import query_gh_latest_release
28 |
29 |
30 | def test_query_gh_latest_release():
31 | latest_version_tag, download_url = query_gh_latest_release(
32 | token=os.getenv("GITHUB_TOKEN", None)
33 | )
34 |
35 | ver = latest_version_tag
36 | assert isinstance(ver, Version)
37 | assert "http" in download_url
38 |
--------------------------------------------------------------------------------
/tests/test_gui.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | from PySide6 import QtWidgets
24 | import pytest
25 | from pytestqt.qtbot import QtBot
26 |
27 | from ansys.tools.installer.main import AnsysPythonInstaller
28 |
29 |
30 | @pytest.fixture(scope="session")
31 | def qtbot_session(qapp, request):
32 | result = QtBot(qapp)
33 | yield result
34 |
35 |
36 | @pytest.fixture(scope="session")
37 | def gui(qtbot_session):
38 | installer = AnsysPythonInstaller(show=False)
39 | yield installer
40 | # qtbot_session.wait(1000)
41 | # installer.close()
42 |
43 |
44 | def test_main_window_header(gui):
45 | assert isinstance(gui.menu_heading, QtWidgets.QLabel)
46 |
47 | # verify image loaded
48 | pixmap = gui.menu_heading.pixmap()
49 | assert pixmap is not None
50 | assert not pixmap.isNull()
51 |
52 |
53 | def test_downloader(gui):
54 | # this URL is subject to change
55 | url = "https://cdn.jsdelivr.net/gh/belaviyo/download-with/samples/sample.png"
56 |
57 | files = []
58 |
59 | def when_finished(out_path):
60 | files.append(out_path)
61 |
62 | gui._download(url, "sample.png", when_finished=when_finished)
63 | assert len(files) == 1
64 | assert files[0].endswith("sample.png")
65 |
--------------------------------------------------------------------------------
/tests/test_linux_functions.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | from ansys.tools.installer.linux_functions import (
24 | get_conda_url_and_filename,
25 | get_vanilla_url_and_filename,
26 | )
27 |
28 |
29 | def test_get_vanilla_url_and_filename():
30 | url, filename = get_vanilla_url_and_filename("3.12.0")
31 | assert url == "https://www.python.org/ftp/python/3.12.0/Python-3.12.0.tar.xz"
32 | assert filename == "Python-3.12.0.tar.xz"
33 |
34 |
35 | def test_get_conda_url_and_filename():
36 | url, filename = get_conda_url_and_filename("23.1.0-4")
37 | assert (
38 | url
39 | == "https://github.com/conda-forge/miniforge/releases/download/23.1.0-4/Miniforge3-23.1.0-4-Linux-x86_64.sh"
40 | )
41 | assert filename == "Miniforge3-23.1.0-4-Linux-x86_64.sh"
42 |
--------------------------------------------------------------------------------
/tests/test_metadata.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
2 | # SPDX-License-Identifier: MIT
3 | #
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 | from ansys.tools.installer import __version__
24 |
25 |
26 | def test_pkg_version():
27 | assert isinstance(__version__, str)
28 |
--------------------------------------------------------------------------------
/uninstall.nsi:
--------------------------------------------------------------------------------
1 | ; Uninstaller script for Ansys Python Manager
2 |
3 | ; Name and version of the program to be uninstalled are already defined
4 |
5 | Var DeleteDefaultVenvPath
6 | Var DeleteConfiguration
7 |
8 | ; Define the uninstaller section
9 | Section "Uninstall"
10 | ; Prompt the user to confirm uninstallation
11 | MessageBox MB_YESNO|MB_ICONQUESTION "Are you sure you want to uninstall ${PRODUCT_NAME} ${PRODUCT_VERSION}?" IDYES checkDeleteVenvPath
12 | Abort
13 |
14 | checkDeleteVenvPath:
15 | MessageBox MB_YESNO|MB_ICONQUESTION "Do you want to delete the contents in the default virtual environment path location?" IDYES deleteVenvPath
16 | StrCpy $DeleteDefaultVenvPath 0
17 | Goto checkDeleteConfiguration
18 |
19 | deleteVenvPath:
20 | StrCpy $DeleteDefaultVenvPath 1
21 | Goto checkDeleteConfiguration
22 |
23 | checkDeleteConfiguration:
24 | MessageBox MB_YESNO|MB_ICONQUESTION "Do you want to delete the Ansys Python Manager stored configuration?" IDYES deleteConfiguration
25 | StrCpy $DeleteConfiguration 0
26 | Goto doneAsking
27 |
28 | deleteConfiguration:
29 | StrCpy $DeleteConfiguration 1
30 | Goto doneAsking
31 |
32 | doneAsking:
33 |
34 | ; Echo the values of $DeleteConfiguration and $DeleteDefaultVenvPath
35 | DetailPrint "User home: $PROFILE"
36 | DetailPrint "DeleteConfiguration: $DeleteConfiguration"
37 | DetailPrint "DeleteDefaultVenvPath: $DeleteDefaultVenvPath"
38 |
39 |
40 | ; Delete directories if required
41 | ${If} $DeleteDefaultVenvPath == 1
42 | RMDir /r "$PROFILE\.ansys_python_venvs"
43 | ${EndIf}
44 | ${If} $DeleteConfiguration == 1
45 | RMDir /r "$PROFILE\.ansys\ansys_python_manager"
46 | ${EndIf}
47 |
48 | ; Remove the installed files
49 | Delete "$INSTDIR\*.*"
50 | RMDir /r /REBOOTOK "$INSTDIR"
51 |
52 | ; Remove the registry keys
53 | DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
54 |
55 | ; Remove the start menu shortcut and directory
56 | Delete "$SMPROGRAMS\Ansys Python Manager\Ansys Python Manager.lnk"
57 | RMDir /r /REBOOTOK "$SMPROGRAMS\Ansys Python Manager"
58 |
59 | ; Display the uninstallation complete message
60 | MessageBox MB_OK|MB_ICONINFORMATION "${PRODUCT_NAME} ${PRODUCT_VERSION} has been successfully uninstalled."
61 | SectionEnd
62 |
--------------------------------------------------------------------------------