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 = [] |
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 """ |
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 [] |