Sat, 30 Jun 2018 13:59:56 +0200
Configuration pages for Python: harmonized the Python debugger pages and the Python page and added a button to show the virtual environment manager dialog to the Python configuration pages.
# -*- coding: utf-8 -*- # Copyright (c) 2018 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a class to manage Python virtual environments. """ from __future__ import unicode_literals import os import sys import shutil import json import copy from PyQt5.QtCore import pyqtSlot, QObject from PyQt5.QtWidgets import QDialog from E5Gui import E5MessageBox import Preferences class VirtualenvManager(QObject): """ Class implementing an object to manage Python virtual environments. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent object @type QWidget """ super(VirtualenvManager, self).__init__(parent) self.__ui = parent self.__loadSettings() self.__virtualenvManagerDialog = None def __loadSettings(self): """ Private slot to load the virtual environments. """ venvString = Preferences.Prefs.settings.value( "PyVenv/VirtualEnvironments", "{}") # __IGNORE_WARNING_M613__ environments = json.loads(venvString) self.__virtualEnvironments = {} # each environment entry is a dictionary: # path: the directory of the virtual environment # (empty for a global environment) # interpreter: the path of the Python interpreter # variant: Python variant (2 or 3) # is_global: a flag indicating a global environment # for venvName in environments: interpreter = environments[venvName]["interpreter"] if os.access(interpreter, os.X_OK): environment = environments[venvName] if "is_global" not in environment: environment["is_global"] = environment["path"] == "" self.__virtualEnvironments[venvName] = environment # check, if the interpreter used to run eric is in the environments defaultPy = sys.executable.replace("w.exe", ".exe") found = False for venvName in self.__virtualEnvironments: if (defaultPy == self.__virtualEnvironments[venvName]["interpreter"]): found = True break if not found: # add an environment entry for the default interpreter self.__virtualEnvironments["<default>"] = { "path": "", "interpreter": defaultPy, "variant": sys.version_info[0], "is_global": True, } self.__saveSettings() def __saveSettings(self): """ Private slot to save the virtual environments. """ Preferences.Prefs.settings.setValue( "PyVenv/VirtualEnvironments", json.dumps(self.__virtualEnvironments) ) @pyqtSlot() def createVirtualEnv(self): """ Public slot to create a new virtual environment. """ from .VirtualenvConfigurationDialog import \ VirtualenvConfigurationDialog dlg = VirtualenvConfigurationDialog() if dlg.exec_() == QDialog.Accepted: (pyvenv, args, name, openTarget, createLog, createScript, targetDir, interpreter) = dlg.getData() # now do the call from .VirtualenvExecDialog import VirtualenvExecDialog dia = VirtualenvExecDialog(pyvenv, targetDir, name, openTarget, createLog, createScript, interpreter, self) dia.show() dia.start(args) dia.exec_() def addVirtualEnv(self, venvName, venvDirectory, venvInterpreter="", venvVariant=3, isGlobal=False): """ Public method to add a virtual environment. @param venvName logical name for the virtual environment @type str @param venvDirectory directory of the virtual environment @type str @param venvInterpreter interpreter of the virtual environment @type str @param venvVariant Python variant of the virtual environment @type int @param isGlobal flag indicating a global environment @type bool """ if venvName in self.__virtualEnvironments: ok = E5MessageBox.yesNo( None, self.tr("Add Virtual Environment"), self.tr("""A virtual environment named <b>{0}</b> exists""" """ already. Shall it be replaced?""") .format(venvName), icon=E5MessageBox.Warning) if not ok: return if not venvInterpreter: from .VirtualenvInterpreterSelectionDialog import \ VirtualenvInterpreterSelectionDialog dlg = VirtualenvInterpreterSelectionDialog(venvName, venvDirectory) if dlg.exec_() == QDialog.Accepted: venvInterpreter, venvVariant = dlg.getData() isGlobal = True if venvInterpreter: self.__virtualEnvironments[venvName] = { "path": venvDirectory, "interpreter": venvInterpreter, "variant": venvVariant, "is_global": isGlobal, } self.__saveSettings() if self.__virtualenvManagerDialog: self.__virtualenvManagerDialog.refresh() def setVirtualEnv(self, venvName, venvDirectory, venvInterpreter, venvVariant, isGlobal): """ Public method to change a virtual environment. @param venvName logical name of the virtual environment @type str @param venvDirectory directory of the virtual environment @type str @param venvInterpreter interpreter of the virtual environment @type str @param venvVariant Python variant of the virtual environment @type int @param isGlobal flag indicating a global environment @type bool """ if venvName not in self.__virtualEnvironments: E5MessageBox.yesNo( None, self.tr("Change Virtual Environment"), self.tr("""A virtual environment named <b>{0}</b> does not""" """ exist. Aborting!""") .format(venvName), icon=E5MessageBox.Warning) return self.__virtualEnvironments[venvName] = { "path": venvDirectory, "interpreter": venvInterpreter, "variant": venvVariant, "is_global": isGlobal, } self.__saveSettings() if self.__virtualenvManagerDialog: self.__virtualenvManagerDialog.refresh() def renameVirtualEnv(self, oldVenvName, venvName, venvDirectory, venvInterpreter, venvVariant, isGlobal): """ Public method to substitute a virtual environment entry with a new name. @param oldVenvName old name of the virtual environment @type str @param venvName logical name for the virtual environment @type str @param venvDirectory directory of the virtual environment @type str @param venvInterpreter interpreter of the virtual environment @type str @param venvVariant Python variant of the virtual environment @type int @param isGlobal flag indicating a global environment @type bool """ if oldVenvName not in self.__virtualEnvironments: E5MessageBox.yesNo( None, self.tr("Rename Virtual Environment"), self.tr("""A virtual environment named <b>{0}</b> does not""" """ exist. Aborting!""") .format(oldVenvName), icon=E5MessageBox.Warning) return del self.__virtualEnvironments[oldVenvName] self.addVirtualEnv(venvName, venvDirectory, venvInterpreter, venvVariant, isGlobal) def deleteVirtualEnvs(self, venvNames): """ Public method to delete virtual environments from the list and disk. @param venvNames list of logical names for the virtual environments @type list of str """ venvMessages = [] for venvName in venvNames: if venvName in self.__virtualEnvironments and \ bool(self.__virtualEnvironments[venvName]["path"]): venvMessages.append(self.tr("{0} - {1}").format( venvName, self.__virtualEnvironments[venvName]["path"])) if venvMessages: from UI.DeleteFilesConfirmationDialog import \ DeleteFilesConfirmationDialog dlg = DeleteFilesConfirmationDialog( None, self.tr("Delete Virtual Environments"), self.tr("""Do you really want to delete these virtual""" """ environments?"""), venvMessages ) if dlg.exec_() == QDialog.Accepted: for venvName in venvNames: if self.__isEnvironmentDeleteable(venvName): shutil.rmtree( self.__virtualEnvironments[venvName]["path"], True) del self.__virtualEnvironments[venvName] self.__saveSettings() if self.__virtualenvManagerDialog: self.__virtualenvManagerDialog.refresh() def __isEnvironmentDeleteable(self, venvName): """ Private method to check, if a virtual environment can be deleted from disk. @param venvName name of the virtual environment @type str @return flag indicating it can be deleted @rtype bool """ ok = False if venvName in self.__virtualEnvironments: ok = True ok &= bool(self.__virtualEnvironments[venvName]["path"]) ok &= not self.__virtualEnvironments[venvName]["is_global"] ok &= os.access(self.__virtualEnvironments[venvName]["path"], os.W_OK) return ok def removeVirtualEnvs(self, venvNames): """ Public method to delete virtual environment from the list. @param venvNames list of logical names for the virtual environments @type list of str """ venvMessages = [] for venvName in venvNames: if venvName in self.__virtualEnvironments: venvMessages.append(self.tr("{0} - {1}").format( venvName, self.__virtualEnvironments[venvName]["path"])) if venvMessages: from UI.DeleteFilesConfirmationDialog import \ DeleteFilesConfirmationDialog dlg = DeleteFilesConfirmationDialog( None, self.tr("Remove Virtual Environments"), self.tr("""Do you really want to remove these virtual""" """ environments?"""), venvMessages ) if dlg.exec_() == QDialog.Accepted: for venvName in venvNames: if venvName in self.__virtualEnvironments: del self.__virtualEnvironments[venvName] self.__saveSettings() if self.__virtualenvManagerDialog: self.__virtualenvManagerDialog.refresh() def getEnvironmentEntries(self): """ Public method to get a dictionary containing the defined virtual environment entries. @return dictionary containing a copy of the defined virtual environments @rtype dict """ return copy.deepcopy(self.__virtualEnvironments) @pyqtSlot() def showVirtualenvManagerDialog(self, modal=False): """ Public slot to show the virtual environment manager dialog. @param modal flag indicating that the dialog should be shown in a blocking mode """ if self.__virtualenvManagerDialog is None: from .VirtualenvManagerDialog import VirtualenvManagerDialog self.__virtualenvManagerDialog = VirtualenvManagerDialog( self, self.__ui) if modal: self.__virtualenvManagerDialog.exec_() else: self.__virtualenvManagerDialog.show() def shutdown(self): """ Public method to shutdown the manager. """ if self.__virtualenvManagerDialog is not None: self.__virtualenvManagerDialog.close() self.__virtualenvManagerDialog = None def isUnique(self, venvName): """ Public method to check, if the give logical name is unique. @param venvName logical name for the virtual environment @type str @return flag indicating uniqueness @rtype bool """ return venvName not in self.__virtualEnvironments def getVirtualenvInterpreter(self, venvName): """ Public method to get the interpreter for a virtual environment. @param venvName logical name for the virtual environment @type str @return interpreter path @rtype str """ if venvName in self.__virtualEnvironments: return self.__virtualEnvironments[venvName]["interpreter"] else: return "" def getVirtualenvDirectory(self, venvName): """ Public method to get the directory of a virtual environment. @param venvName logical name for the virtual environment @type str @return directory path @rtype str """ if venvName in self.__virtualEnvironments: return self.__virtualEnvironments[venvName]["path"] else: return "" def getVirtualenvNames(self): """ Public method to get a list of defined virtual environments. @return list of defined virtual environments @rtype list of str """ return list(self.__virtualEnvironments.keys()) def getVirtualenvNamesForVariant(self, variant): """ Public method to get a list of virtual environments for a given Python variant. @param variant Python variant (2 or 3) @type int @return list of defined virtual environments @rtype list of str """ assert variant in (2, 3) environments = [] for venvName in self.__virtualEnvironments: if self.__virtualEnvironments[venvName]["variant"] == variant: environments.append(venvName) return environments def isGlobalEnvironment(self, venvName): """ Public method to test, if a given environment is a global one. @param venvName logical name of the virtual environment @type str @return flag indicating a global environment @rtype bool """ return self.__virtualEnvironments[venvName]["is_global"]