Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ansys/tools/installer/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

PYTHON_VERSION_SELECTION_FOR_VENV = """Choose the version of Python to use for your virtual environment.

Please select the Python version from the table below to create its respective virtual environment. Currently Conda Forge Versions are not supported."""
Please select the Python version from the table below to create its respective virtual environment."""

NAME_FOR_VENV = f"""Provide the name for your virtual environment.<br><br>Virtual environments are created under user directory <i>{ANSYS_VENVS}</i>. If the name provided already exists for another virtual environment, it will not be created. Users will receive a warning informing of the situation."""

Expand Down
55 changes: 18 additions & 37 deletions src/ansys/tools/installer/create_virtual_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
)
from ansys.tools.installer.installed_table import DataTable

ALLOWED_FOCUS_EVENTS = [QtCore.QEvent.WindowActivate, QtCore.QEvent.Show]
ALLOWED_FOCUS_EVENTS = [QtCore.QEvent.Type.WindowActivate, QtCore.QEvent.Type.Show]

LOG = logging.getLogger(__name__)
LOG.setLevel("DEBUG")
Expand Down Expand Up @@ -86,22 +86,20 @@ def __init__(self, parent):

def create_venv(self):
"""Create virtual environment at selected directory."""
user_directory = os.path.expanduser("~")
venv_dir = ANSYS_VENVS
user_venv_dir = f"{user_directory}/{venv_dir}/{self.venv_name.text()}"
isExist = os.path.exists(user_venv_dir)
venv_dir = Path(Path.home(), ANSYS_VENVS, self.venv_name.text())

if self.venv_name.text() == "":
self.failed_to_create_dialog(case_1=True)
elif isExist:
elif venv_dir.exists():
self.failed_to_create_dialog(case_2=True)
else:
Path(f"{user_directory}/{venv_dir}/{self.venv_name.text()}").mkdir(
parents=True, exist_ok=True
)
user_venv_dir = f"{user_directory}/{venv_dir}/{self.venv_name.text()}"
cmd = f'python -m venv "{user_venv_dir}"'
self.launch_cmd(cmd, minimized_window=True, exit_cmd="^& exit")
venv_dir.mkdir(parents=True, exist_ok=True)

try:
self.cmd_create_venv(venv_dir)
except:
self.failed_to_create_dialog()

self.update_table()
self.venv_success_dialog()

Expand Down Expand Up @@ -160,42 +158,25 @@ def eventFilter(self, source, event):
self.table.setFocus()
return super().eventFilter(source, event)

def launch_cmd(self, extra="", minimized_window=False, exit_cmd=""):
"""Run a command in a new command prompt.
def cmd_create_venv(self, venv_dir):
"""Create a virtual environment in a new command prompt.

Parameters
----------
extra : str, default: ""
Any additional command(s).
minimized_window : bool, default: False
Whether the window should run minimized or not.
venv_dir : str
The location for the virtual environment.
"""
py_path = self.table.active_path
user_profile = os.path.expanduser("~")
min_win = "/w /min" if minimized_window else ""

if "Python" in self.table.active_version:
scripts_path = os.path.join(py_path, "Scripts")
new_path = f"{py_path};{scripts_path};%PATH%"

if extra:
cmd = f"& {extra} {exit_cmd}"
else:
cmd = f"& echo Python set to {py_path}"

subprocess.call(
f'start {min_win} cmd /K "set PATH={new_path};{cmd}"{exit_cmd}',
f'start /w /min cmd /K "set PATH={new_path} && python -m venv {venv_dir} && exit"',
shell=True,
cwd=user_profile,
)
else: # probably conda
if extra:
# Replace the pip install command for conda
extra = extra.replace("pip", "conda")
cmd = f"& {extra} {exit_cmd}"
else:
cmd = f"& echo Activating conda forge at path {py_path}"
else: # conda
subprocess.call(
f'start {min_win} cmd /K "{py_path}\\Scripts\\activate.bat {py_path}&cd %userprofile%{cmd}"{exit_cmd}',
f'start /w /min cmd /K "{py_path}\\Scripts\\activate.bat && conda create --prefix {venv_dir} -y && exit"',
shell=True,
cwd=user_profile,
)
171 changes: 114 additions & 57 deletions src/ansys/tools/installer/installed_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
get_all_python_venv,
)

ALLOWED_FOCUS_EVENTS = [QtCore.QEvent.WindowActivate, QtCore.QEvent.Show]
ALLOWED_FOCUS_EVENTS = [QtCore.QEvent.Type.WindowActivate, QtCore.QEvent.Type.Show]
LOG = logging.getLogger(__name__)
LOG.setLevel("DEBUG")

Expand Down Expand Up @@ -147,10 +147,10 @@ def __init__(self, parent=None):
self.setLayout(layout)

# Group 1: Available Virtual Environments
available_venv_box = QtWidgets.QGroupBox("Available virtual environments")
self.available_venv_box = QtWidgets.QGroupBox("Available virtual environments")
available_venv_box_layout = QtWidgets.QVBoxLayout()
available_venv_box_layout.setContentsMargins(10, 20, 10, 20)
available_venv_box.setLayout(available_venv_box_layout)
self.available_venv_box.setLayout(available_venv_box_layout)

# --> Add text for available virtual environments
available_venv_box_text = QtWidgets.QLabel()
Expand All @@ -164,7 +164,35 @@ def __init__(self, parent=None):
self.venv_table.setSelectionMode(QtWidgets.QTableWidget.SingleSelection)

available_venv_box_layout.addWidget(self.venv_table)
layout.addWidget(available_venv_box)
layout.addWidget(self.available_venv_box)

# EXTRA Group: Available Python installation
self.available_python_install_box = QtWidgets.QGroupBox(
"Available base Python versions"
)
available_python_install_box_layout = QtWidgets.QVBoxLayout()
available_python_install_box_layout.setContentsMargins(10, 20, 10, 20)
self.available_python_install_box.setLayout(available_python_install_box_layout)

# Python Version, Forge Version Table
self.table = DataTable(installed_python=True, installed_forge=True)
self.table.setSelectionMode(QtWidgets.QTableWidget.SingleSelection)
available_python_install_box_layout.addWidget(self.table)
layout.addWidget(self.available_python_install_box)

# Hide it at first
self.available_python_install_box.hide()

# EXTRA: Use general Python installations for the above actions
self.check_box_opt = QtWidgets.QCheckBox(
"NOT RECOMMENDED: Use base Python versions instead of virtual environments."
)
self.check_box_opt.setCheckState(QtCore.Qt.CheckState.Unchecked)
layout.addWidget(self.check_box_opt)
self.check_box_opt.stateChanged.connect(self.set_chk_box_focus)
self.check_box_opt.stateChanged.connect(self.display_ctrl)

####### Launching and package management options #######

# Group 2: Launching Options
launching_options_box = QtWidgets.QGroupBox("Launching options")
Expand Down Expand Up @@ -234,6 +262,7 @@ def __init__(self, parent=None):

self.package_pip_dict = {
"PyAnsys-Metapackage": "pyansys",
"PyAnsys-Math": "ansys-math-core",
"PyAEDT": "pyaedt",
"PyDPF-Core": "ansys-dpf-core",
"PyDPF-Post": "ansys-dpf-post",
Expand All @@ -243,9 +272,12 @@ def __init__(self, parent=None):
"PyFluent-Visualization": "ansys-fluent-visualization",
"PyMAPDL": "ansys-mapdl-core",
"PyMAPDL Reader": "ansys-mapdl-reader",
"PyMechanical": "ansys-mechanical-core",
"PyMotorCAD": "ansys-motorcad-core",
"PyPIM": "ansys-platform-instancemanagement",
"PyPrimeMesh": "ansys-meshing-prime",
"PySeascape": "ansys-seascape",
"PySystem Coupling": "ansys-systemcoupling-core",
"PyTwin": "pytwin",
"Granta MI BoM Analytics": "ansys-grantami-bomanalytics",
"Shared Components": "ansys-openapi-common",
Expand All @@ -272,34 +304,6 @@ def __init__(self, parent=None):
hbox_install_pyansys.addWidget(self.button_launch_cmd)
layout.addWidget(pyansys_pkg_manage_box)

# EXTRA: Use general Python installations for the above actions
self.check_box_opt = QtWidgets.QCheckBox(
"NOT RECOMMENDED: Use base Python environments for the above actions."
)
self.check_box_opt.setCheckState(QtCore.Qt.CheckState.Unchecked)
layout.addWidget(self.check_box_opt)
self.check_box_opt.stateChanged.connect(self.set_chk_box_focus)
self.check_box_opt.stateChanged.connect(self.display_ctrl)

# Group 5: Available Python installation
self.available_python_install_box = QtWidgets.QGroupBox(
"Available Python installations"
)
self.available_python_install_box_layout = QtWidgets.QVBoxLayout()
self.available_python_install_box_layout.setContentsMargins(10, 20, 10, 20)
self.available_python_install_box.setLayout(
self.available_python_install_box_layout
)

# Python Version, Forge Version Table
self.table = DataTable(installed_python=True, installed_forge=True)
self.table.setSelectionMode(QtWidgets.QTableWidget.SingleSelection)
self.available_python_install_box_layout.addWidget(self.table)
layout.addWidget(self.available_python_install_box)

self.isHidden = True
self.available_python_install_box.hide()

# ensure the table is always in focus
self.installEventFilter(self)

Expand All @@ -310,12 +314,12 @@ def update_table(self):

def display_ctrl(self):
"""Set the focus accordingly depending on check box."""
if self.isHidden:
if self.is_chk_box_active():
self.available_python_install_box.show()
self.isHidden = False
self.available_venv_box.hide()
else:
self.available_python_install_box.hide()
self.isHidden = True
self.available_venv_box.show()

def set_chk_box_focus(self, state):
"""Set the focus accordingly depending on check box."""
Expand Down Expand Up @@ -381,13 +385,18 @@ def list_packages(self):
self.launch_cmd("pip list")

def _update_pck_mnger(self):
"""Update package manager if needed."""
cmd = ""
if "Python" in self.table.active_version:
cmd = "python -m pip install -U pip & exit"
elif "Conda" in self.table.active_version:
cmd = "conda update conda & exit"
self.launch_cmd(cmd, True)
"""Update package manager if needed.

Notes
-----
Only working on base Python installations, for now.
"""
if self.is_chk_box_active():
if "Python" in self.table.active_version:
cmd = "python -m pip install -U pip && exit"
else: # Otherwise, conda
cmd = "conda update conda --yes && exit"
self.launch_cmd(cmd, minimized_window=True)

def launch_cmd(self, extra="", minimized_window=False):
"""Run a command in a new command prompt.
Expand All @@ -399,43 +408,91 @@ def launch_cmd(self, extra="", minimized_window=False):
minimized_window : bool, default: False
Whether the window should run minimized or not.
"""
min_win = "/w /min" if minimized_window else ""

# is_venv - True : virtual environment , False: base python installation
# is_vanilla_python - True : Vanilla Python , False : Miniforge/Conda

if self.is_chk_box_active():
is_venv = False
# when base python installation is chosen in the table
py_path = self.table.active_path
py_version = self.table.active_version
# chosen_general_base_is_python = True if "Python" in py_version else False

miniforge_path = "" if "Python" in py_version else py_path
is_vanilla_python = True if miniforge_path == "" else False
else:
is_venv = True
# when virtual environment is chosen in the table
py_path = self.venv_table.active_path

min_win = "/w /min" if minimized_window else ""
if "Python" in self.table.active_version and self.is_chk_box_active():
parent_path = os.path.dirname(py_path) # No Scripts Folder
# If py_path has a folder called conda-meta . then it is a conda environment
is_vanilla_python = (
False if "conda-meta" in os.listdir(parent_path) else True
)
if is_vanilla_python:
miniforge_path = ""
else:
py_path = os.path.dirname(py_path) # No Scripts Folder
# If it is a conda environment, then we need to get the path to the miniforge installation
with open(os.path.join(py_path, "conda-meta", "history"), "r") as f:
for line in f:
if line.startswith("# cmd:"):
line = line.lstrip("# cmd: ")
path = line.strip().split("create --prefix")[0]
miniforge_path = (
path.strip().split("Scripts")[0].rstrip("\\")
)
break
is_vanilla_python = True if miniforge_path == "" else False

if is_vanilla_python and not is_venv:
scripts_path = os.path.join(py_path, "Scripts")
new_path = f"{py_path};{scripts_path};%PATH%"

if extra:
cmd = f"& {extra}"
cmd = f"&& {extra}"
else:
cmd = f"& echo Python set to {py_path}"
cmd = f"&& echo Python set to {py_path}"

subprocess.call(
f'start {min_win} cmd /K "set PATH={new_path}&cd %userprofile%{cmd}"',
f'start {min_win} cmd /K "set PATH={new_path} && cd %userprofile% {cmd}"',
shell=True,
)
elif is_vanilla_python and is_venv:
# Launch with active python virtual environment
if extra:
cmd = f"&& {extra}"
else:
cmd = f"&& echo Python set to {py_path}"
subprocess.call(
f'start {min_win} cmd /K "{py_path}\\activate.bat {py_path} && cd %userprofile% {cmd}"',
shell=True,
)
elif not self.is_chk_box_active():
# Launch with active virtual environment
elif not is_vanilla_python and is_venv:
# Launch with active conda virtual environment
if extra:
cmd = f"& {extra}"
# Replace the pip install command for conda
extra = extra.replace("pip", "conda")
extra = extra.replace("conda install", "conda install --yes")
cmd = f"&& {extra}"
else:
cmd = f"& echo Python set to {py_path}"
cmd = f"&& echo Activating conda forge at path {py_path}"
subprocess.call(
f'start {min_win} cmd /K "{py_path}\\activate.bat {py_path}&cd %userprofile%{cmd}"',
f'start {min_win} cmd /K "{miniforge_path}\\Scripts\\activate.bat && conda activate {py_path} && cd %userprofile% {cmd}"',
shell=True,
)
else: # probably conda
else:
# not is_vanilla_python and not is_venv
if extra:
# Replace the pip install command for conda
extra = extra.replace("pip", "conda")
cmd = f"& {extra}"
extra = extra.replace("conda install", "conda install --yes")
cmd = f"&& {extra}"
else:
cmd = f"& echo Activating conda forge at path {py_path}"
cmd = f"&& echo Activating conda forge at path {py_path}"
subprocess.call(
f'start {min_win} cmd /K "{py_path}\\Scripts\\activate.bat {py_path}&cd %userprofile%{cmd}"',
f'start {min_win} cmd /K "{miniforge_path}\\Scripts\\activate.bat && conda activate {py_path} && cd %userprofile% {cmd}"',
shell=True,
)