Skip to content
Merged
Changes from 6 commits
Commits
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
131 changes: 105 additions & 26 deletions src/ansys/tools/installer/installed_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import logging
import os
import shutil
import subprocess
import time

Expand Down Expand Up @@ -116,6 +117,9 @@ def populate(self):
self.resizeColumnsToContents()
self.selectRow(0)
self.horizontalHeader().setStretchLastSection(True)
self.horizontalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.ResizeMode.Fixed
)

self.destroyed.connect(self.stop)
self._locked = False
Expand All @@ -124,16 +128,21 @@ def stop(self):
"""Flag that this object is gone."""
self._destroyed = True

@property
def active_path(self):
"""Path of the active row."""
return self.item(self.currentRow(), 2).text()

@property
def active_version(self):
"""Version of the active row."""
return self.item(self.currentRow(), 0).text()

@property
def active_admin(self):
"""Version of the active row."""
return self.item(self.currentRow(), 1).text()

@property
def active_path(self):
"""Path of the active row."""
return self.item(self.currentRow(), 2).text()


class InstalledTab(QtWidgets.QWidget):
"""Installed Python versions tab."""
Expand Down Expand Up @@ -164,6 +173,10 @@ def __init__(self, parent=None):
self.venv_table.setSelectionMode(QtWidgets.QTableWidget.SingleSelection)

available_venv_box_layout.addWidget(self.venv_table)
self.venv_table.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.venv_table.customContextMenuRequested.connect(
self.delete_virtual_environment
)
layout.addWidget(self.available_venv_box)

# EXTRA Group: Available Python installation
Expand Down Expand Up @@ -398,6 +411,91 @@ def _update_pck_mnger(self):
cmd = "conda update conda --yes && exit"
self.launch_cmd(cmd, minimized_window=True)

def find_env_type(self, table_name):
"""Find if this is a conda or vanilla python environment.

Parameters
----------
table_name : str
The name of the table to be used ("venv_table" or "table")

Returns
-------
tuple(str or bool, str or None, str or None)
A tuple containing information on: if it is a pip environment or conda
environment, the path to the Miniforge installation if any, and the path
to the virtual environment's activation.
"""
if table_name == "venv_table":
sel_table = self.venv_table
elif table_name == "table":
sel_table = self.table
else:
LOG.error("No table provided. Internal tool error.")
return None, None, None

py_path = sel_table.active_path
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
# Fail-safe check
if not is_vanilla_python and miniforge_path == "":
LOG.error("Invalid type of virtual environment. Internal tool error.")
return None, None, None

return is_vanilla_python, miniforge_path, parent_path

def delete_virtual_environment(self, point):
"""Delete Virtual Environment Using Right Click."""
# Get the cell that was right-clicked
index = self.venv_table.indexAt(point)
if not index.isValid():
return

# Create the context menu
menu = QtWidgets.QMenu(self)
delete_action = menu.addAction("Delete virtual environment")

# Show the context menu and handle the user's choice
action = menu.exec(self.venv_table.mapToGlobal(point))

# Get information on the venv type
is_vanilla_python, miniforge_path, parent_path = self.find_env_type(
"venv_table"
)

if is_vanilla_python and action == delete_action:
try:
# Delete the python virtual environment
shutil.rmtree(parent_path)
except:
pass
elif not is_vanilla_python and action == delete_action:
try:
# Delete the conda environment
subprocess.call(
f'start /w /min cmd /K "{miniforge_path}\\Scripts\\activate.bat && conda env remove --prefix {parent_path} --yes && exit"',
shell=True,
)
except:
pass

# Finally, update the venv table
self.venv_table.update()

def launch_cmd(self, extra="", minimized_window=False):
"""Run a command in a new command prompt.

Expand All @@ -424,28 +522,9 @@ def launch_cmd(self, extra="", minimized_window=False):
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
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
is_vanilla_python, miniforge_path, py_path = self.find_env_type(
"venv_table"
)
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")
Expand Down