Virtual Environments eric7

Wed, 29 Nov 2023 14:27:25 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 29 Nov 2023 14:27:25 +0100
branch
eric7
changeset 10351
1f9bafeff96c
parent 10348
b2297669c065
child 10352
37d8616b44ca

Virtual Environments
- Added the capability to search for unregistered Python interpreters in order to create an environment entry for those selected by the user.

src/eric7/SystemUtilities/PythonUtilities.py file | annotate | diff | comparison | revisions
src/eric7/VirtualEnv/VirtualenvManager.py file | annotate | diff | comparison | revisions
src/eric7/VirtualEnv/VirtualenvManagerWidget.ui file | annotate | diff | comparison | revisions
src/eric7/VirtualEnv/VirtualenvManagerWidgets.py file | annotate | diff | comparison | revisions
--- a/src/eric7/SystemUtilities/PythonUtilities.py	Wed Nov 29 11:33:32 2023 +0100
+++ b/src/eric7/SystemUtilities/PythonUtilities.py	Wed Nov 29 14:27:25 2023 +0100
@@ -7,10 +7,14 @@
 Module implementing Python related utility functions.
 """
 
+import contextlib
 import os
+import platform
 import sys
 import sysconfig
 
+from .OSUtilities import isWindowsPlatform
+
 
 def getPythonExecutable():
     """
@@ -135,3 +139,135 @@
             pyVer = 3
 
     return pyVer
+
+
+def searchInterpreters(environments=None):
+    """
+    Function to determine a list of all Python interpreters available via the
+    executable search path (i.e. PATH) (Windows variant).
+
+    @param environments list of environment directories to scan for Python interpreters
+        (defaults to None)
+    @type list of str (optional)
+    @return list of found interpreter executables
+    @rtype list of str
+    """
+    if isWindowsPlatform():
+        return __searchInterpreters_Windows(environments=environments)
+    else:
+        return __searchInterpreters_Linux(environments=environments)
+
+
+def __searchInterpreters_Windows(environments=None):
+    """
+    Function to determine a list of all Python interpreters available via the
+    executable search path (i.e. PATH) (Windows variant).
+
+    @param environments list of environment directories to scan for Python interpreters
+        (defaults to None)
+    @type list of str (optional)
+    @return list of found interpreter executables
+    @rtype list of str
+    """
+    try:
+        import winreg
+    except ImportError:
+        import _winreg as winreg  # __IGNORE_WARNING__
+
+    def getExePath(branch, access, versionStr):
+        with contextlib.suppress(WindowsError, OSError):
+            software = winreg.OpenKey(branch, "Software", 0, access)
+            python = winreg.OpenKey(software, "Python", 0, access)
+            pcore = winreg.OpenKey(python, "PythonCore", 0, access)
+            version = winreg.OpenKey(pcore, versionStr, 0, access)
+            installpath = winreg.QueryValue(version, "InstallPath")
+            exe = os.path.join(installpath,  "python.exe")
+            if os.access(exe, os.X_OK):
+                return exe
+
+        return None
+
+    minorVersions = range(8, 16)  # Py 3.8 until Py 3.15
+    interpreters = set()
+
+    if environments:
+        for directory in [os.path.join(d, "Scripts") for d in environments]:
+            exe = os.path.join(directory, "python.exe")
+            if os.access(exe, os.X_OK):
+                interpreters.add(exe)
+
+    else:
+        versionSuffixes = ["", "-32", "-64"]
+        for minorVersion in minorVersions:
+            for versionSuffix in versionSuffixes:
+                versionStr = "{0}.{1}{2}".format(
+                    "3", minorVersion, versionSuffix
+                )
+                exePath = getExePath(
+                    winreg.HKEY_CURRENT_USER,
+                    winreg.KEY_WOW64_32KEY | winreg.KEY_READ,
+                    versionStr,
+                )
+                if exePath:
+                    interpreters.add(exePath)
+
+                exePath = getExePath(
+                    winreg.HKEY_LOCAL_MACHINE,
+                    winreg.KEY_WOW64_32KEY | winreg.KEY_READ,
+                    versionStr,
+                )
+                if exePath:
+                    interpreters.add(exePath)
+
+                # Even on Intel 64-bit machines it's 'AMD64'
+                if platform.machine() == "AMD64":
+                    exePath = getExePath(
+                        winreg.HKEY_CURRENT_USER,
+                        winreg.KEY_WOW64_64KEY | winreg.KEY_READ,
+                        versionStr,
+                    )
+                    if exePath:
+                        interpreters.add(exePath)
+
+                    exePath = getExePath(
+                        winreg.HKEY_LOCAL_MACHINE,
+                        winreg.KEY_WOW64_64KEY | winreg.KEY_READ,
+                        versionStr,
+                    )
+                    if exePath:
+                        interpreters.add(exePath)
+
+    return list(interpreters)
+
+
+def __searchInterpreters_Linux(environments=None):
+    """
+    Function to determine a list of all Python interpreters available via the
+    executable search path (i.e. PATH) (non Windows variant).
+
+    @param environments list of environment directories to scan for Python interpreters
+        (defaults to None)
+    @type list of str (optional)
+    @return list of found interpreter executables
+    @rtype list of str
+    """
+    from eric7.SystemUtilities import OSUtilities
+
+    minorVersions = range(8, 16)  # Py 3.8 until Py 3.15
+    interpreters = []
+
+    if environments:
+        directories = [os.path.join(d, "bin") for d in environments]
+    else:
+        searchpath = OSUtilities.getEnvironmentEntry("PATH")
+        directories = searchpath.split(os.pathsep) if searchpath else []
+
+    if directories:
+        pythonNames = ["python3.{0}".format(v) for v in minorVersions]
+        for directory in directories:
+            for interpreter in pythonNames:
+                exe = os.path.join(directory, interpreter)
+                if os.access(exe, os.X_OK):
+                    interpreters.append(exe)
+
+    return interpreters
--- a/src/eric7/VirtualEnv/VirtualenvManager.py	Wed Nov 29 11:33:32 2023 +0100
+++ b/src/eric7/VirtualEnv/VirtualenvManager.py	Wed Nov 29 14:27:25 2023 +0100
@@ -20,7 +20,7 @@
 from eric7 import Preferences
 from eric7.EricWidgets import EricMessageBox
 from eric7.EricWidgets.EricApplication import ericApp
-from eric7.SystemUtilities import FileSystemUtilities, PythonUtilities
+from eric7.SystemUtilities import FileSystemUtilities, OSUtilities, PythonUtilities
 from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
 
 from .VirtualenvMeta import VirtualenvMetaData
@@ -479,6 +479,33 @@
                 self.virtualEnvironmentRemoved.emit()
                 self.virtualEnvironmentsListChanged.emit()
 
+    def searchUnregisteredInterpreters(self):
+        """
+        Public method to search for unregistered Python interpreters.
+
+        @return list of unregistered interpreters
+        @rtype list of str
+        """
+        interpreters = []
+        baseDir = self.getVirtualEnvironmentsBaseDir()
+        if not baseDir:
+            # search in home directory, if no environments base directory is defined
+            baseDir = OSUtilities.getHomeDir()
+        environments = [
+            os.path.join(baseDir, d)
+            for d in os.listdir(baseDir)
+            if os.path.isdir(os.path.join(baseDir, d))
+        ]
+
+        interpreters = PythonUtilities.searchInterpreters()
+        if environments:
+            interpreters += PythonUtilities.searchInterpreters(environments)
+
+        interpreters = {
+            i for i in interpreters if not self.environmentForInterpreter(i)[0]
+        }  # filter the list into a set to make the remaining ones unique
+        return list(interpreters)
+
     def getEnvironmentEntries(self):
         """
         Public method to get a list of the defined virtual environment entries.
--- a/src/eric7/VirtualEnv/VirtualenvManagerWidget.ui	Wed Nov 29 11:33:32 2023 +0100
+++ b/src/eric7/VirtualEnv/VirtualenvManagerWidget.ui	Wed Nov 29 14:27:25 2023 +0100
@@ -70,6 +70,13 @@
       </widget>
      </item>
      <item>
+      <widget class="QToolButton" name="searchNewButton">
+       <property name="toolTip">
+        <string>Search the execution path for all Python interpreters not configured in an environment yet.</string>
+       </property>
+      </widget>
+     </item>
+     <item>
       <widget class="Line" name="line_6">
        <property name="orientation">
         <enum>Qt::Vertical</enum>
@@ -265,6 +272,7 @@
   <tabstop>refreshButton</tabstop>
   <tabstop>addButton</tabstop>
   <tabstop>newButton</tabstop>
+  <tabstop>searchNewButton</tabstop>
   <tabstop>editButton</tabstop>
   <tabstop>upgradeButton</tabstop>
   <tabstop>removeButton</tabstop>
--- a/src/eric7/VirtualEnv/VirtualenvManagerWidgets.py	Wed Nov 29 11:33:32 2023 +0100
+++ b/src/eric7/VirtualEnv/VirtualenvManagerWidgets.py	Wed Nov 29 14:27:25 2023 +0100
@@ -8,6 +8,7 @@
 environments.
 """
 
+import datetime
 import os
 
 from PyQt6.QtCore import Qt, pyqtSlot
@@ -21,11 +22,14 @@
 )
 
 from eric7.EricGui import EricPixmapCache
+from eric7.EricWidgets import EricMessageBox
 from eric7.EricWidgets.EricMainWindow import EricMainWindow
 from eric7.EricWidgets.EricPathPicker import EricPathPickerModes
+from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog
 from eric7.SystemUtilities import OSUtilities
 
 from .Ui_VirtualenvManagerWidget import Ui_VirtualenvManagerWidget
+from .VirtualenvMeta import VirtualenvMetaData
 
 
 class VirtualenvManagerWidget(QWidget, Ui_VirtualenvManagerWidget):
@@ -55,6 +59,7 @@
         self.refreshButton.setIcon(EricPixmapCache.getIcon("reload"))
         self.addButton.setIcon(EricPixmapCache.getIcon("plus"))
         self.newButton.setIcon(EricPixmapCache.getIcon("new"))
+        self.searchNewButton.setIcon(EricPixmapCache.getIcon("question"))
         self.editButton.setIcon(EricPixmapCache.getIcon("edit"))
         self.upgradeButton.setIcon(EricPixmapCache.getIcon("upgrade"))
         self.removeButton.setIcon(EricPixmapCache.getIcon("minus"))
@@ -161,6 +166,73 @@
         self.__manager.createVirtualEnv(baseDir=self.envBaseDirectoryPicker.text())
 
     @pyqtSlot()
+    def on_searchNewButton_clicked(self):
+        """
+        Public slot to search for new (not yet registered) Python interpreters.
+        """
+        potentialInterpreters = self.__manager.searchUnregisteredInterpreters()
+
+        if not bool(potentialInterpreters):
+            EricMessageBox.information(
+                self,
+                self.tr("Search Virtual Environments"),
+                self.tr("""No unregistered virtual environments were found."""),
+            )
+            return
+
+        baseDir = self.__manager.getVirtualEnvironmentsBaseDir()
+        if not baseDir:
+            baseDir = OSUtilities.getHomeDir()
+
+        selectionList = []
+        for interpreter in potentialInterpreters:
+            if not interpreter.startswith(baseDir):
+                realpath = os.path.realpath(interpreter)
+                if realpath != interpreter:
+                    # interpreter is a link
+                    selectionList.append(
+                        (
+                            self.tr("{0}\n(=> {1})").format(interpreter, realpath),
+                            interpreter,
+                        )
+                    )
+                    continue
+
+            selectionList.append((interpreter, interpreter))
+
+        dlg = EricListSelectionDialog(
+            sorted(selectionList),
+            title=self.tr("Search Virtual Environments"),
+            message=self.tr(
+                "Select the interpreters to create environment entries for:"
+            ),
+            checkBoxSelection=True,
+            emptySelectionOk=True,
+            showSelectAll=True,
+        )
+        if dlg.exec() == QDialog.DialogCode.Accepted:
+            selectedInterpreters = [env[1] for env in dlg.getSelection()]
+
+            nameTemplate = (
+                "Environment #{0} added "
+                + datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
+            )
+            for interpreter in selectedInterpreters:
+                metadata = VirtualenvMetaData(
+                    name=nameTemplate.format(
+                        selectedInterpreters.index(interpreter) + 1
+                    ),
+                    path=(
+                        os.path.abspath(os.path.join(interpreter, "..", ".."))
+                        if interpreter.startswith(baseDir)
+                        else ""
+                    ),
+                    interpreter=interpreter,
+                    is_global=not interpreter.startswith(baseDir),
+                )
+                self.__manager.addVirtualEnv(metadata)
+
+    @pyqtSlot()
     def on_editButton_clicked(self):
         """
         Private slot to edit the selected entry.

eric ide

mercurial