src/eric7/VirtualEnv/VirtualenvManager.py

branch
eric7
changeset 11230
8a15b05eeee3
parent 11148
15e30f0c76a8
equal deleted inserted replaced
11229:16a129d168f9 11230:8a15b05eeee3
18 from PyQt6.QtWidgets import QDialog 18 from PyQt6.QtWidgets import QDialog
19 19
20 from eric7 import Preferences 20 from eric7 import Preferences
21 from eric7.EricWidgets import EricMessageBox 21 from eric7.EricWidgets import EricMessageBox
22 from eric7.EricWidgets.EricApplication import ericApp 22 from eric7.EricWidgets.EricApplication import ericApp
23 from eric7.EricWidgets.EricComboSelectionDialog import EricComboSelectionDialog
23 from eric7.SystemUtilities import FileSystemUtilities, OSUtilities, PythonUtilities 24 from eric7.SystemUtilities import FileSystemUtilities, OSUtilities, PythonUtilities
24 from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog 25 from eric7.UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog
25 26
26 from .VirtualenvMeta import VirtualenvMetaData 27 from .VirtualenvMeta import VirtualenvMetaData
28 from .VirtualenvRegistry import VirtualenvType, VirtualenvTypeRegistry
27 29
28 30
29 class VirtualenvManager(QObject): 31 class VirtualenvManager(QObject):
30 """ 32 """
31 Class implementing an object to manage Python virtual environments. 33 Class implementing an object to manage Python virtual environments.
58 """ 60 """
59 super().__init__(parent) 61 super().__init__(parent)
60 62
61 self.__ui = parent 63 self.__ui = parent
62 64
65 self.__registry = VirtualenvTypeRegistry(venvManager=self)
66 self.__virtualEnvironments = {}
67
68 # register built-in virtual environment types
69 self.__registry.registerType(
70 VirtualenvType(
71 name="standard",
72 visual_name=self.tr("Standard"),
73 createFunc=self.__createStandardVirtualEnv,
74 deleteFunc=self.__deleteStandardVirtualEnv,
75 )
76 )
77 self.__registry.registerType(
78 VirtualenvType(name="remote", visual_name=self.tr("Remote"))
79 )
80 self.__registry.registerType(
81 VirtualenvType(name="eric_server", visual_name=self.tr("eric-ide Server"))
82 )
83
63 self.__loadSettings() 84 self.__loadSettings()
64 85
65 def __loadSettings(self): 86 def __loadSettings(self):
66 """ 87 """
67 Private slot to load the virtual environments. 88 Private slot to load the virtual environments.
68 """ 89 """
69 self.__virtualEnvironmentsBaseDir = Preferences.getSettings().value( 90 self.__virtualEnvironmentsBaseDir = Preferences.getSettings().value(
70 "PyVenv/VirtualEnvironmentsBaseDir", "" 91 "PyVenv/VirtualEnvironmentsBaseDir", ""
71 ) 92 )
72 93
73 venvString = Preferences.getSettings().value( 94 for key in ("PyVenv/VirtualEnvironmentsV2", "PyVenv/VirtualEnvironments"):
74 "PyVenv/VirtualEnvironments", "{}" # __IGNORE_WARNING_M-613__ 95 venvString = Preferences.getSettings().value(key, "{}") # noqa: M-613
75 ) 96 environments = json.loads(venvString)
76 environments = json.loads(venvString) 97 if environments:
98 break
77 99
78 self.__virtualEnvironments = {} 100 self.__virtualEnvironments = {}
79 # each environment entry is a dictionary: 101 # each environment entry is a VirtualenvMetaData object:
80 # path: the directory of the virtual environment
81 # (empty for a global environment)
82 # interpreter: the path of the Python interpreter
83 # variant: Python variant (always 3)
84 # is_global: a flag indicating a global environment
85 # is_conda: a flag indicating an Anaconda environment
86 # is_remote: a flag indicating a remotely accessed environment
87 # is_eric_server a flag indicating an eric-ide server environment
88 # eric_server a string giving the server name in case of an
89 # eric-ide server environment
90 # exec_path: a string to be prefixed to the PATH environment
91 # setting
92 # description a description of the environment
93 #
94 for venvName in environments: 102 for venvName in environments:
95 environment = environments[venvName] 103 environment = environments[venvName]
96 environment["name"] = venvName 104 environment["name"] = venvName
97 if ( 105 if (
98 environment["is_remote"] 106 environment.get("environment_type", "standard") == "remote"
107 or environment.get("is_remote", False) # meta data V1
99 or os.access(environment["interpreter"], os.X_OK) 108 or os.access(environment["interpreter"], os.X_OK)
100 ) and "is_global" not in environment: 109 ) and "is_global" not in environment:
101 environment["is_global"] = environment["path"] == "" 110 environment["is_global"] = environment["path"] == ""
102 111
103 self.__virtualEnvironments[venvName] = VirtualenvMetaData.from_dict( 112 self.__virtualEnvironments[venvName] = VirtualenvMetaData.from_dict(
138 Preferences.getSettings().setValue( 147 Preferences.getSettings().setValue(
139 "PyVenv/VirtualEnvironmentsBaseDir", self.__virtualEnvironmentsBaseDir 148 "PyVenv/VirtualEnvironmentsBaseDir", self.__virtualEnvironmentsBaseDir
140 ) 149 )
141 150
142 Preferences.getSettings().setValue( 151 Preferences.getSettings().setValue(
143 "PyVenv/VirtualEnvironments", 152 "PyVenv/VirtualEnvironmentsV2",
144 json.dumps( 153 json.dumps(
145 {env.name: env.as_dict() for env in self.__virtualEnvironments.values()} 154 {env.name: env.as_dict() for env in self.__virtualEnvironments.values()}
146 ), 155 ),
147 ) 156 )
148 Preferences.syncPreferences() 157 Preferences.syncPreferences()
161 """ 170 """
162 removed = False 171 removed = False
163 172
164 for venvName in list(self.__virtualEnvironments): 173 for venvName in list(self.__virtualEnvironments):
165 venvItem = self.__virtualEnvironments[venvName] 174 venvItem = self.__virtualEnvironments[venvName]
166 if not venvItem.is_remote: 175 if venvItem.environment_type != "remote":
167 venvPath = venvItem.path 176 venvPath = venvItem.path
168 if venvPath: 177 if venvPath:
169 if venvItem.is_eric_server: 178 if venvItem.environment_type == "eric_server":
170 with contextlib.suppress(KeyError): 179 with contextlib.suppress(KeyError):
171 # It is an eric-ide server environment; check it is 180 # It is an eric-ide server environment; check it is
172 # still valid. 181 # still valid.
173 ericServer = ericApp().getObject("EricServer") 182 ericServer = ericApp().getObject("EricServer")
174 if ( 183 if (
236 @pyqtSlot() 245 @pyqtSlot()
237 def createVirtualEnv(self, baseDir=""): 246 def createVirtualEnv(self, baseDir=""):
238 """ 247 """
239 Public slot to create a new virtual environment. 248 Public slot to create a new virtual environment.
240 249
241 @param baseDir base directory for the virtual environments 250 @param baseDir base directory for the virtual environments (defaults to "")
242 @type str 251 @type str (optional)
252 """
253 if not baseDir:
254 baseDir = self.__virtualEnvironmentsBaseDir
255
256 environmentTypes = self.__registry.getCreatableEnvironmentTypes()
257 if len(environmentTypes) == 1:
258 environmentTypes[0].createFunc(baseDir=baseDir)
259 elif len(environmentTypes) > 1:
260 dlg = EricComboSelectionDialog(
261 [(t.visual_name, t.name) for t in environmentTypes],
262 title=self.tr("Create Virtual Environment"),
263 message=self.tr("Select the virtual environment type:"),
264 parent=self.__ui,
265 )
266 if dlg.exec() == QDialog.DialogCode.Accepted:
267 selectedVenvType = dlg.getSelection()[1]
268 for venvType in environmentTypes:
269 if venvType.name == selectedVenvType:
270 venvType.createFunc(baseDir=baseDir)
271 break
272
273 def __createStandardVirtualEnv(self, baseDir=""):
274 """
275 Private method to create a standard (pyvenv or virtualenv) environment.
276
277 @param baseDir base directory for the virtual environments (defaults to "")
278 @type str (optional)
243 """ 279 """
244 from .VirtualenvConfigurationDialog import VirtualenvConfigurationDialog 280 from .VirtualenvConfigurationDialog import VirtualenvConfigurationDialog
245 from .VirtualenvExecDialog import VirtualenvExecDialog 281 from .VirtualenvExecDialog import VirtualenvExecDialog
246
247 if not baseDir:
248 baseDir = self.__virtualEnvironmentsBaseDir
249 282
250 dlg = VirtualenvConfigurationDialog(baseDir=baseDir, parent=self.__ui) 283 dlg = VirtualenvConfigurationDialog(baseDir=baseDir, parent=self.__ui)
251 if dlg.exec() == QDialog.DialogCode.Accepted: 284 if dlg.exec() == QDialog.DialogCode.Accepted:
252 resultDict = dlg.getData() 285 resultDict = dlg.getData()
253 286 # now do the call
254 if resultDict["envType"] == "conda": 287 dia = VirtualenvExecDialog(resultDict, self, parent=self.__ui)
255 # create the conda environment 288 dia.show()
256 conda = ericApp().getObject("Conda") 289 dia.start(resultDict["arguments"])
257 ok, prefix, interpreter = conda.createCondaEnvironment( 290 dia.exec()
258 resultDict["arguments"]
259 )
260 if ok and "--dry-run" not in resultDict["arguments"]:
261 self.addVirtualEnv(
262 VirtualenvMetaData(
263 name=resultDict["logicalName"],
264 path=prefix,
265 interpreter=interpreter,
266 is_conda=True,
267 )
268 )
269 else:
270 # now do the call
271 dia = VirtualenvExecDialog(resultDict, self, parent=self.__ui)
272 dia.show()
273 dia.start(resultDict["arguments"])
274 dia.exec()
275 291
276 @pyqtSlot() 292 @pyqtSlot()
277 def upgradeVirtualEnv(self, venvName): 293 def upgradeVirtualEnv(self, venvName):
278 """ 294 """
279 Public slot to upgrade a virtual environment. 295 Public slot to upgrade a virtual environment.
430 ), 446 ),
431 venvMessages, 447 venvMessages,
432 ) 448 )
433 if dlg.exec() == QDialog.DialogCode.Accepted: 449 if dlg.exec() == QDialog.DialogCode.Accepted:
434 for venvName in venvNames: 450 for venvName in venvNames:
435 if self.__isEnvironmentDeleteable(venvName): 451 envType = self.__registry.getEnvironmentType(
436 if self.isCondaEnvironment(venvName): 452 self.__virtualEnvironments[venvName].environment_type
437 conda = ericApp().getObject("Conda") 453 )
438 path = self.__virtualEnvironments[venvName].path 454 if envType and envType.deleteFunc:
439 res = conda.removeCondaEnvironment(prefix=path) 455 deleted = envType.deleteFunc(
440 if res: 456 self.__virtualEnvironments[venvName]
441 del self.__virtualEnvironments[venvName] 457 )
442 else: 458 if deleted:
443 shutil.rmtree(
444 self.__virtualEnvironments[venvName].path,
445 ignore_errors=True,
446 )
447 del self.__virtualEnvironments[venvName] 459 del self.__virtualEnvironments[venvName]
448 460
449 self.__saveSettings() 461 self.__saveSettings()
450 462
451 self.virtualEnvironmentRemoved.emit() 463 self.virtualEnvironmentRemoved.emit()
464 ok = False 476 ok = False
465 if venvName in self.__virtualEnvironments: 477 if venvName in self.__virtualEnvironments:
466 ok = True 478 ok = True
467 ok &= bool(self.__virtualEnvironments[venvName].path) 479 ok &= bool(self.__virtualEnvironments[venvName].path)
468 ok &= not self.__virtualEnvironments[venvName].is_global 480 ok &= not self.__virtualEnvironments[venvName].is_global
469 ok &= not self.__virtualEnvironments[venvName].is_remote
470 ok &= not self.__virtualEnvironments[venvName].is_eric_server
471 ok &= os.access(self.__virtualEnvironments[venvName].path, os.W_OK) 481 ok &= os.access(self.__virtualEnvironments[venvName].path, os.W_OK)
472 482
473 return ok 483 return ok
474 484
485 def __deleteStandardVirtualEnv(self, venvMetaData):
486 """
487 Private method to delete a given virtual environment from disk.
488
489 @param venvMetaData virtual environment meta data structure
490 @type VirtualenvMetaData
491 @return flag indicating success
492 @rtype bool
493 """
494 if self.__isEnvironmentDeleteable(venvMetaData.name):
495 shutil.rmtree(venvMetaData.path, ignore_errors=True)
496 return True
497 else:
498 return False
499
475 def removeVirtualEnvs(self, venvNames): 500 def removeVirtualEnvs(self, venvNames):
476 """ 501 """
477 Public method to delete virtual environment from the list. 502 Public method to delete virtual environments from the list.
478 503
479 @param venvNames list of logical names for the virtual environments 504 @param venvNames list of logical names for the virtual environments
480 @type list of str 505 @type list of str
481 """ 506 """
482 venvMessages = [] 507 venvMessages = []
529 if environments: 554 if environments:
530 interpreters += PythonUtilities.searchInterpreters(environments) 555 interpreters += PythonUtilities.searchInterpreters(environments)
531 556
532 interpreters = { 557 interpreters = {
533 i for i in interpreters if not self.environmentForInterpreter(i)[0] 558 i for i in interpreters if not self.environmentForInterpreter(i)[0]
534 } # filter the list into a set to make the remaining ones unique 559 } # convert the list into a set to make the remaining ones unique
535 return list(interpreters) 560 return list(interpreters)
536 561
537 def getEnvironmentEntries(self): 562 def getEnvironmentEntries(self):
538 """ 563 """
539 Public method to get a list of the defined virtual environment entries. 564 Public method to get a list of the defined virtual environment entries.
618 if venvName in self.__virtualEnvironments: 643 if venvName in self.__virtualEnvironments:
619 return self.__virtualEnvironments[venvName].path 644 return self.__virtualEnvironments[venvName].path
620 else: 645 else:
621 return "" 646 return ""
622 647
623 def getVirtualenvNames( 648 def getVirtualenvNames(self, noGlobals=False, filterList=("all",)):
624 self, noRemote=False, noConda=False, noGlobals=False, noServer=False
625 ):
626 """ 649 """
627 Public method to get a list of defined virtual environments. 650 Public method to get a list of defined virtual environments.
628 651
629 @param noRemote flag indicating to exclude environments for remote
630 debugging (defaults to False)
631 @type bool (optional)
632 @param noConda flag indicating to exclude Conda environments (defaults to False)
633 @type bool (optional)
634 @param noGlobals flag indicating to exclude global environments 652 @param noGlobals flag indicating to exclude global environments
635 (defaults to False) 653 (defaults to False)
636 @type bool (optional) 654 @type bool (optional)
637 @param noServer flag indicating to exclude eric-ide server environments 655 @param filterList tuple containing the list of virtual environment types to
638 (defaults to False) 656 be included (prefixed by +) or excluded (prefixed by -) (defaults to
639 @type bool (optional) 657 ("all",) )
658 @type tuple of str ((optional)
640 @return list of defined virtual environments 659 @return list of defined virtual environments
641 @rtype list of str 660 @rtype list of str
642 """ 661 """
643 environments = list(self.__virtualEnvironments) 662 environments = list(self.__virtualEnvironments)
644 if noRemote:
645 environments = [
646 name for name in environments if not self.isRemoteEnvironment(name)
647 ]
648 if noConda:
649 environments = [
650 name for name in environments if not self.isCondaEnvironment(name)
651 ]
652 if noGlobals: 663 if noGlobals:
653 environments = [ 664 environments = [
654 name for name in environments if not self.isGlobalEnvironment(name) 665 name for name in environments if not self.isGlobalEnvironment(name)
655 ] 666 ]
656 if noServer: 667 if filterList != ("all",):
657 environments = [ 668 includeFilter = [f[1:] for f in filterList if f.startswith("+")]
658 name for name in environments if not self.isEricServerEnvironment(name) 669 excludeFilter = [f[1:] for f in filterList if f.startswith("-")]
659 ] 670 if includeFilter:
671 environments = [
672 name
673 for name in environments
674 if self.__virtualEnvironments[name].environment_type
675 in includeFilter
676 ]
677 if excludeFilter:
678 environments = [
679 name
680 for name in environments
681 if self.__virtualEnvironments[name].environment_type
682 not in excludeFilter
683 ]
660 684
661 return environments 685 return environments
662 686
663 def isGlobalEnvironment(self, venvName): 687 def isGlobalEnvironment(self, venvName):
664 """ 688 """
669 @return flag indicating a global environment 693 @return flag indicating a global environment
670 @rtype bool 694 @rtype bool
671 """ 695 """
672 try: 696 try:
673 return self.__virtualEnvironments[venvName].is_global 697 return self.__virtualEnvironments[venvName].is_global
674 except KeyError:
675 return False
676
677 def isCondaEnvironment(self, venvName):
678 """
679 Public method to test, if a given environment is an Anaconda
680 environment.
681
682 @param venvName logical name of the virtual environment
683 @type str
684 @return flag indicating an Anaconda environment
685 @rtype bool
686 """
687 try:
688 return self.__virtualEnvironments[venvName].is_conda
689 except KeyError:
690 return False
691
692 def isRemoteEnvironment(self, venvName):
693 """
694 Public method to test, if a given environment is a remotely accessed
695 environment.
696
697 @param venvName logical name of the virtual environment
698 @type str
699 @return flag indicating a remotely accessed environment
700 @rtype bool
701 """
702 try:
703 return self.__virtualEnvironments[venvName].is_remote
704 except KeyError: 698 except KeyError:
705 return False 699 return False
706 700
707 def getVirtualenvExecPath(self, venvName): 701 def getVirtualenvExecPath(self, venvName):
708 """ 702 """
752 """ 746 """
753 try: 747 try:
754 if host: 748 if host:
755 return self.__virtualEnvironments[ 749 return self.__virtualEnvironments[
756 venvName 750 venvName
757 ].is_eric_server and self.__virtualEnvironments[ 751 ].environment_type == "eric_server" and self.__virtualEnvironments[
758 venvName 752 venvName
759 ].eric_server.startswith( 753 ].eric_server.startswith(
760 f"{host}:" 754 f"{host}:"
761 ) 755 )
762 else: 756 else:
763 return self.__virtualEnvironments[venvName].is_eric_server 757 return (
758 self.__virtualEnvironments[venvName].environment_type
759 == "eric_server"
760 )
764 except KeyError: 761 except KeyError:
765 return False 762 return False
766 763
767 def getEricServerEnvironmentNames(self, host=""): 764 def getEricServerEnvironmentNames(self, host=""):
768 """ 765 """
778 for name in self.__virtualEnvironments 775 for name in self.__virtualEnvironments
779 if self.isEricServerEnvironment(name, host=host) 776 if self.isEricServerEnvironment(name, host=host)
780 ] 777 ]
781 778
782 return environments 779 return environments
780
781 #######################################################################
782 ## Interface to the virtual environment types registry
783 #######################################################################
784
785 def getEnvironmentTypesRegistry(self):
786 """
787 Public method to get a reference to the virtual environment types registry
788 object.
789
790 @return reference to the virtual environment types registry object
791 @rtype VirtualenvTypeRegistry
792 """
793 return self.__registry
794
795 def registerType(self, venvType):
796 """
797 Public method to register a new virtual environment type.
798
799 @param venvType virtual environment data
800 @type VirtualenvType
801 """
802 self.__registry.registerType(venvType=venvType)
803
804 def unregisterType(self, name):
805 """
806 Public method to unregister the virtual environment type of the given name.
807
808 @param name name of the virtual environment type
809 @type str
810 """
811 self.__registry.unregisterType(name=name)
812
813 def getEnvironmentTypeNames(self):
814 """
815 Public method to get a list of names of registered virtual environment types.
816
817 @return list of tuples of virtual environment type names and their visual name
818 @rtype list of tuple of (str, str)
819 """
820 return self.__registry.getEnvironmentTypeNames() if self.__registry else []

eric ide

mercurial