src/eric7/VirtualEnv/VirtualenvManager.py

branch
eric7
changeset 11230
8a15b05eeee3
parent 11148
15e30f0c76a8
--- a/src/eric7/VirtualEnv/VirtualenvManager.py	Wed Apr 23 17:23:57 2025 +0200
+++ b/src/eric7/VirtualEnv/VirtualenvManager.py	Wed Apr 23 18:02:09 2025 +0200
@@ -20,10 +20,12 @@
 from eric7 import Preferences
 from eric7.EricWidgets import EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
+from eric7.EricWidgets.EricComboSelectionDialog import EricComboSelectionDialog
 from eric7.SystemUtilities import FileSystemUtilities, OSUtilities, PythonUtilities
 from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
 
 from .VirtualenvMeta import VirtualenvMetaData
+from .VirtualenvRegistry import VirtualenvType, VirtualenvTypeRegistry
 
 
 class VirtualenvManager(QObject):
@@ -60,6 +62,25 @@
 
         self.__ui = parent
 
+        self.__registry = VirtualenvTypeRegistry(venvManager=self)
+        self.__virtualEnvironments = {}
+
+        # register built-in virtual environment types
+        self.__registry.registerType(
+            VirtualenvType(
+                name="standard",
+                visual_name=self.tr("Standard"),
+                createFunc=self.__createStandardVirtualEnv,
+                deleteFunc=self.__deleteStandardVirtualEnv,
+            )
+        )
+        self.__registry.registerType(
+            VirtualenvType(name="remote", visual_name=self.tr("Remote"))
+        )
+        self.__registry.registerType(
+            VirtualenvType(name="eric_server", visual_name=self.tr("eric-ide Server"))
+        )
+
         self.__loadSettings()
 
     def __loadSettings(self):
@@ -70,32 +91,20 @@
             "PyVenv/VirtualEnvironmentsBaseDir", ""
         )
 
-        venvString = Preferences.getSettings().value(
-            "PyVenv/VirtualEnvironments", "{}"  # __IGNORE_WARNING_M-613__
-        )
-        environments = json.loads(venvString)
+        for key in ("PyVenv/VirtualEnvironmentsV2", "PyVenv/VirtualEnvironments"):
+            venvString = Preferences.getSettings().value(key, "{}")  # noqa: M-613
+            environments = json.loads(venvString)
+            if environments:
+                break
 
         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 (always 3)
-        #   is_global:      a flag indicating a global environment
-        #   is_conda:       a flag indicating an Anaconda environment
-        #   is_remote:      a flag indicating a remotely accessed environment
-        #   is_eric_server  a flag indicating an eric-ide server environment
-        #   eric_server     a string giving the server name in case of an
-        #                   eric-ide server environment
-        #   exec_path:      a string to be prefixed to the PATH environment
-        #                   setting
-        #   description     a description of the environment
-        #
+        # each environment entry is a VirtualenvMetaData object:
         for venvName in environments:
             environment = environments[venvName]
             environment["name"] = venvName
             if (
-                environment["is_remote"]
+                environment.get("environment_type", "standard") == "remote"
+                or environment.get("is_remote", False)  # meta data V1
                 or os.access(environment["interpreter"], os.X_OK)
             ) and "is_global" not in environment:
                 environment["is_global"] = environment["path"] == ""
@@ -140,7 +149,7 @@
         )
 
         Preferences.getSettings().setValue(
-            "PyVenv/VirtualEnvironments",
+            "PyVenv/VirtualEnvironmentsV2",
             json.dumps(
                 {env.name: env.as_dict() for env in self.__virtualEnvironments.values()}
             ),
@@ -163,10 +172,10 @@
 
         for venvName in list(self.__virtualEnvironments):
             venvItem = self.__virtualEnvironments[venvName]
-            if not venvItem.is_remote:
+            if venvItem.environment_type != "remote":
                 venvPath = venvItem.path
                 if venvPath:
-                    if venvItem.is_eric_server:
+                    if venvItem.environment_type == "eric_server":
                         with contextlib.suppress(KeyError):
                             # It is an eric-ide server environment; check it is
                             # still valid.
@@ -238,40 +247,47 @@
         """
         Public slot to create a new virtual environment.
 
-        @param baseDir base directory for the virtual environments
-        @type str
+        @param baseDir base directory for the virtual environments (defaults to "")
+        @type str (optional)
+        """
+        if not baseDir:
+            baseDir = self.__virtualEnvironmentsBaseDir
+
+        environmentTypes = self.__registry.getCreatableEnvironmentTypes()
+        if len(environmentTypes) == 1:
+            environmentTypes[0].createFunc(baseDir=baseDir)
+        elif len(environmentTypes) > 1:
+            dlg = EricComboSelectionDialog(
+                [(t.visual_name, t.name) for t in environmentTypes],
+                title=self.tr("Create Virtual Environment"),
+                message=self.tr("Select the virtual environment type:"),
+                parent=self.__ui,
+            )
+            if dlg.exec() == QDialog.DialogCode.Accepted:
+                selectedVenvType = dlg.getSelection()[1]
+                for venvType in environmentTypes:
+                    if venvType.name == selectedVenvType:
+                        venvType.createFunc(baseDir=baseDir)
+                        break
+
+    def __createStandardVirtualEnv(self, baseDir=""):
+        """
+        Private method to create a standard (pyvenv or virtualenv) environment.
+
+        @param baseDir base directory for the virtual environments (defaults to "")
+        @type str (optional)
         """
         from .VirtualenvConfigurationDialog import VirtualenvConfigurationDialog
         from .VirtualenvExecDialog import VirtualenvExecDialog
 
-        if not baseDir:
-            baseDir = self.__virtualEnvironmentsBaseDir
-
         dlg = VirtualenvConfigurationDialog(baseDir=baseDir, parent=self.__ui)
         if dlg.exec() == QDialog.DialogCode.Accepted:
             resultDict = dlg.getData()
-
-            if resultDict["envType"] == "conda":
-                # create the conda environment
-                conda = ericApp().getObject("Conda")
-                ok, prefix, interpreter = conda.createCondaEnvironment(
-                    resultDict["arguments"]
-                )
-                if ok and "--dry-run" not in resultDict["arguments"]:
-                    self.addVirtualEnv(
-                        VirtualenvMetaData(
-                            name=resultDict["logicalName"],
-                            path=prefix,
-                            interpreter=interpreter,
-                            is_conda=True,
-                        )
-                    )
-            else:
-                # now do the call
-                dia = VirtualenvExecDialog(resultDict, self, parent=self.__ui)
-                dia.show()
-                dia.start(resultDict["arguments"])
-                dia.exec()
+            # now do the call
+            dia = VirtualenvExecDialog(resultDict, self, parent=self.__ui)
+            dia.show()
+            dia.start(resultDict["arguments"])
+            dia.exec()
 
     @pyqtSlot()
     def upgradeVirtualEnv(self, venvName):
@@ -432,18 +448,14 @@
             )
             if dlg.exec() == QDialog.DialogCode.Accepted:
                 for venvName in venvNames:
-                    if self.__isEnvironmentDeleteable(venvName):
-                        if self.isCondaEnvironment(venvName):
-                            conda = ericApp().getObject("Conda")
-                            path = self.__virtualEnvironments[venvName].path
-                            res = conda.removeCondaEnvironment(prefix=path)
-                            if res:
-                                del self.__virtualEnvironments[venvName]
-                        else:
-                            shutil.rmtree(
-                                self.__virtualEnvironments[venvName].path,
-                                ignore_errors=True,
-                            )
+                    envType = self.__registry.getEnvironmentType(
+                        self.__virtualEnvironments[venvName].environment_type
+                    )
+                    if envType and envType.deleteFunc:
+                        deleted = envType.deleteFunc(
+                            self.__virtualEnvironments[venvName]
+                        )
+                        if deleted:
                             del self.__virtualEnvironments[venvName]
 
                 self.__saveSettings()
@@ -466,15 +478,28 @@
             ok = True
             ok &= bool(self.__virtualEnvironments[venvName].path)
             ok &= not self.__virtualEnvironments[venvName].is_global
-            ok &= not self.__virtualEnvironments[venvName].is_remote
-            ok &= not self.__virtualEnvironments[venvName].is_eric_server
             ok &= os.access(self.__virtualEnvironments[venvName].path, os.W_OK)
 
         return ok
 
+    def __deleteStandardVirtualEnv(self, venvMetaData):
+        """
+        Private method to delete a given virtual environment from disk.
+
+        @param venvMetaData virtual environment meta data structure
+        @type VirtualenvMetaData
+        @return flag indicating success
+        @rtype bool
+        """
+        if self.__isEnvironmentDeleteable(venvMetaData.name):
+            shutil.rmtree(venvMetaData.path, ignore_errors=True)
+            return True
+        else:
+            return False
+
     def removeVirtualEnvs(self, venvNames):
         """
-        Public method to delete virtual environment from the list.
+        Public method to delete virtual environments from the list.
 
         @param venvNames list of logical names for the virtual environments
         @type list of str
@@ -531,7 +556,7 @@
 
         interpreters = {
             i for i in interpreters if not self.environmentForInterpreter(i)[0]
-        }  # filter the list into a set to make the remaining ones unique
+        }  # convert the list into a set to make the remaining ones unique
         return list(interpreters)
 
     def getEnvironmentEntries(self):
@@ -620,43 +645,42 @@
         else:
             return ""
 
-    def getVirtualenvNames(
-        self, noRemote=False, noConda=False, noGlobals=False, noServer=False
-    ):
+    def getVirtualenvNames(self, noGlobals=False, filterList=("all",)):
         """
         Public method to get a list of defined virtual environments.
 
-        @param noRemote flag indicating to exclude environments for remote
-            debugging (defaults to False)
-        @type bool (optional)
-        @param noConda flag indicating to exclude Conda environments (defaults to False)
-        @type bool (optional)
         @param noGlobals flag indicating to exclude global environments
             (defaults to False)
         @type bool (optional)
-        @param noServer flag indicating to exclude eric-ide server environments
-            (defaults to False)
-        @type bool (optional)
+        @param filterList tuple containing the list of virtual environment types to
+            be included (prefixed by +) or excluded (prefixed by -) (defaults to
+            ("all",) )
+        @type tuple of str ((optional)
         @return list of defined virtual environments
         @rtype list of str
         """
         environments = list(self.__virtualEnvironments)
-        if noRemote:
-            environments = [
-                name for name in environments if not self.isRemoteEnvironment(name)
-            ]
-        if noConda:
-            environments = [
-                name for name in environments if not self.isCondaEnvironment(name)
-            ]
         if noGlobals:
             environments = [
                 name for name in environments if not self.isGlobalEnvironment(name)
             ]
-        if noServer:
-            environments = [
-                name for name in environments if not self.isEricServerEnvironment(name)
-            ]
+        if filterList != ("all",):
+            includeFilter = [f[1:] for f in filterList if f.startswith("+")]
+            excludeFilter = [f[1:] for f in filterList if f.startswith("-")]
+            if includeFilter:
+                environments = [
+                    name
+                    for name in environments
+                    if self.__virtualEnvironments[name].environment_type
+                    in includeFilter
+                ]
+            if excludeFilter:
+                environments = [
+                    name
+                    for name in environments
+                    if self.__virtualEnvironments[name].environment_type
+                    not in excludeFilter
+                ]
 
         return environments
 
@@ -674,36 +698,6 @@
         except KeyError:
             return False
 
-    def isCondaEnvironment(self, venvName):
-        """
-        Public method to test, if a given environment is an Anaconda
-        environment.
-
-        @param venvName logical name of the virtual environment
-        @type str
-        @return flag indicating an Anaconda environment
-        @rtype bool
-        """
-        try:
-            return self.__virtualEnvironments[venvName].is_conda
-        except KeyError:
-            return False
-
-    def isRemoteEnvironment(self, venvName):
-        """
-        Public method to test, if a given environment is a remotely accessed
-        environment.
-
-        @param venvName logical name of the virtual environment
-        @type str
-        @return flag indicating a remotely accessed environment
-        @rtype bool
-        """
-        try:
-            return self.__virtualEnvironments[venvName].is_remote
-        except KeyError:
-            return False
-
     def getVirtualenvExecPath(self, venvName):
         """
         Public method to get the search path prefix of a virtual environment.
@@ -754,13 +748,16 @@
             if host:
                 return self.__virtualEnvironments[
                     venvName
-                ].is_eric_server and self.__virtualEnvironments[
+                ].environment_type == "eric_server" and self.__virtualEnvironments[
                     venvName
                 ].eric_server.startswith(
                     f"{host}:"
                 )
             else:
-                return self.__virtualEnvironments[venvName].is_eric_server
+                return (
+                    self.__virtualEnvironments[venvName].environment_type
+                    == "eric_server"
+                )
         except KeyError:
             return False
 
@@ -780,3 +777,44 @@
         ]
 
         return environments
+
+    #######################################################################
+    ## Interface to the virtual environment types registry
+    #######################################################################
+
+    def getEnvironmentTypesRegistry(self):
+        """
+        Public method to get a reference to the virtual environment types registry
+        object.
+
+        @return reference to the virtual environment types registry object
+        @rtype VirtualenvTypeRegistry
+        """
+        return self.__registry
+
+    def registerType(self, venvType):
+        """
+        Public method to register a new virtual environment type.
+
+        @param venvType virtual environment data
+        @type VirtualenvType
+        """
+        self.__registry.registerType(venvType=venvType)
+
+    def unregisterType(self, name):
+        """
+        Public method to unregister the virtual environment type of the given name.
+
+        @param name name of the virtual environment type
+        @type str
+        """
+        self.__registry.unregisterType(name=name)
+
+    def getEnvironmentTypeNames(self):
+        """
+        Public method to get a list of names of registered virtual environment types.
+
+        @return list of tuples of virtual environment type names and their visual name
+        @rtype list of tuple of (str, str)
+        """
+        return self.__registry.getEnvironmentTypeNames() if self.__registry else []

eric ide

mercurial