|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2018 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a class to manage Python virtual environments. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import sys |
|
12 import shutil |
|
13 import json |
|
14 import copy |
|
15 |
|
16 from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject |
|
17 from PyQt5.QtWidgets import QDialog |
|
18 |
|
19 from E5Gui import E5MessageBox |
|
20 from E5Gui.E5Application import e5App |
|
21 |
|
22 import Preferences |
|
23 |
|
24 |
|
25 class VirtualenvManager(QObject): |
|
26 """ |
|
27 Class implementing an object to manage Python virtual environments. |
|
28 |
|
29 @signal virtualEnvironmentAdded() emitted to indicate the addition of |
|
30 a virtual environment |
|
31 @signal virtualEnvironmentRemoved() emitted to indicate the removal and |
|
32 deletion of a virtual environment |
|
33 @signal virtualEnvironmentChanged(name) emitted to indicate a change of |
|
34 a virtual environment |
|
35 """ |
|
36 DefaultKey = "<default>" |
|
37 |
|
38 virtualEnvironmentAdded = pyqtSignal() |
|
39 virtualEnvironmentRemoved = pyqtSignal() |
|
40 virtualEnvironmentChanged = pyqtSignal(str) |
|
41 |
|
42 def __init__(self, parent=None): |
|
43 """ |
|
44 Constructor |
|
45 |
|
46 @param parent reference to the parent object |
|
47 @type QWidget |
|
48 """ |
|
49 super().__init__(parent) |
|
50 |
|
51 self.__ui = parent |
|
52 |
|
53 self.__loadSettings() |
|
54 |
|
55 self.__virtualenvManagerDialog = None |
|
56 |
|
57 def __loadSettings(self): |
|
58 """ |
|
59 Private slot to load the virtual environments. |
|
60 """ |
|
61 self.__virtualEnvironmentsBaseDir = Preferences.Prefs.settings.value( |
|
62 "PyVenv/VirtualEnvironmentsBaseDir", "") |
|
63 |
|
64 venvString = Preferences.Prefs.settings.value( |
|
65 "PyVenv/VirtualEnvironments", "{}") # __IGNORE_WARNING_M613__ |
|
66 environments = json.loads(venvString) |
|
67 |
|
68 self.__virtualEnvironments = {} |
|
69 # each environment entry is a dictionary: |
|
70 # path: the directory of the virtual environment |
|
71 # (empty for a global environment) |
|
72 # interpreter: the path of the Python interpreter |
|
73 # variant: Python variant (always 3) |
|
74 # is_global: a flag indicating a global environment |
|
75 # is_conda: a flag indicating an Anaconda environment |
|
76 # is_remote: a flag indicating a remotely accessed environment |
|
77 # exec_path: a string to be prefixed to the PATH environment |
|
78 # setting |
|
79 # |
|
80 envsToDelete = [] |
|
81 for venvName in environments: |
|
82 environment = environments[venvName] |
|
83 if environment["variant"] == 2: |
|
84 # Python2 environment are not supported anymore, delete them |
|
85 envsToDelete.append(venvName) |
|
86 continue |
|
87 |
|
88 if ( |
|
89 ("is_remote" in environment and environment["is_remote"]) or |
|
90 os.access(environment["interpreter"], os.X_OK) |
|
91 ): |
|
92 if "is_global" not in environment: |
|
93 environment["is_global"] = environment["path"] == "" |
|
94 if "is_conda" not in environment: |
|
95 environment["is_conda"] = False |
|
96 if "is_remote" not in environment: |
|
97 environment["is_remote"] = False |
|
98 if "exec_path" not in environment: |
|
99 environment["exec_path"] = "" |
|
100 self.__virtualEnvironments[venvName] = environment |
|
101 |
|
102 # now remove unsupported environments |
|
103 for venvName in envsToDelete: |
|
104 del environments[venvName] |
|
105 |
|
106 # check, if the interpreter used to run eric is in the environments |
|
107 defaultPy = sys.executable.replace("w.exe", ".exe") |
|
108 found = False |
|
109 for venvName in self.__virtualEnvironments: |
|
110 if (defaultPy == |
|
111 self.__virtualEnvironments[venvName]["interpreter"]): |
|
112 found = True |
|
113 break |
|
114 if not found: |
|
115 # add an environment entry for the default interpreter |
|
116 self.__virtualEnvironments[VirtualenvManager.DefaultKey] = { |
|
117 "path": "", |
|
118 "interpreter": defaultPy, |
|
119 "variant": 3, |
|
120 "is_global": True, |
|
121 "is_conda": False, |
|
122 "is_remote": False, |
|
123 "exec_path": "", |
|
124 } |
|
125 |
|
126 self.__saveSettings() |
|
127 |
|
128 def __saveSettings(self): |
|
129 """ |
|
130 Private slot to save the virtual environments. |
|
131 """ |
|
132 Preferences.Prefs.settings.setValue( |
|
133 "PyVenv/VirtualEnvironmentsBaseDir", |
|
134 self.__virtualEnvironmentsBaseDir) |
|
135 |
|
136 Preferences.Prefs.settings.setValue( |
|
137 "PyVenv/VirtualEnvironments", |
|
138 json.dumps(self.__virtualEnvironments) |
|
139 ) |
|
140 Preferences.syncPreferences() |
|
141 |
|
142 def getDefaultEnvironment(self): |
|
143 """ |
|
144 Public method to get the default virtual environment. |
|
145 |
|
146 Default is an environment with the key '<default>' or the first one |
|
147 having an interpreter matching sys.executable (i.e. the one used to |
|
148 execute eric with) |
|
149 |
|
150 @return tuple containing the environment name and a dictionary |
|
151 containing a copy of the default virtual environment |
|
152 @rtype tuple of (str, dict) |
|
153 """ |
|
154 if VirtualenvManager.DefaultKey in self.__virtualEnvironments: |
|
155 return ( |
|
156 VirtualenvManager.DefaultKey, |
|
157 copy.copy( |
|
158 self.__virtualEnvironments[VirtualenvManager.DefaultKey]) |
|
159 ) |
|
160 |
|
161 else: |
|
162 defaultPy = sys.executable.replace("w.exe", ".exe") |
|
163 for venvName in self.__virtualEnvironments: |
|
164 if (defaultPy == |
|
165 self.__virtualEnvironments[venvName]["interpreter"]): |
|
166 return ( |
|
167 venvName, |
|
168 copy.copy(self.__virtualEnvironments[venvName]) |
|
169 ) |
|
170 |
|
171 return ("", {}) |
|
172 |
|
173 @pyqtSlot() |
|
174 def createVirtualEnv(self, baseDir=""): |
|
175 """ |
|
176 Public slot to create a new virtual environment. |
|
177 |
|
178 @param baseDir base directory for the virtual environments |
|
179 @type str |
|
180 """ |
|
181 from .VirtualenvConfigurationDialog import ( |
|
182 VirtualenvConfigurationDialog |
|
183 ) |
|
184 |
|
185 if not baseDir: |
|
186 baseDir = self.__virtualEnvironmentsBaseDir |
|
187 |
|
188 dlg = VirtualenvConfigurationDialog(baseDir=baseDir) |
|
189 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
190 resultDict = dlg.getData() |
|
191 |
|
192 if resultDict["envType"] == "conda": |
|
193 # create the conda environment |
|
194 conda = e5App().getObject("Conda") |
|
195 ok, prefix, interpreter = conda.createCondaEnvironment( |
|
196 resultDict["arguments"]) |
|
197 if ok and "--dry-run" not in resultDict["arguments"]: |
|
198 self.addVirtualEnv(resultDict["logicalName"], |
|
199 prefix, |
|
200 venvInterpreter=interpreter, |
|
201 isConda=True) |
|
202 else: |
|
203 # now do the call |
|
204 from .VirtualenvExecDialog import VirtualenvExecDialog |
|
205 dia = VirtualenvExecDialog(resultDict, self) |
|
206 dia.show() |
|
207 dia.start(resultDict["arguments"]) |
|
208 dia.exec() |
|
209 |
|
210 def addVirtualEnv(self, venvName, venvDirectory, venvInterpreter="", |
|
211 isGlobal=False, isConda=False, isRemote=False, |
|
212 execPath=""): |
|
213 """ |
|
214 Public method to add a virtual environment. |
|
215 |
|
216 @param venvName logical name for the virtual environment |
|
217 @type str |
|
218 @param venvDirectory directory of the virtual environment |
|
219 @type str |
|
220 @param venvInterpreter interpreter of the virtual environment |
|
221 @type str |
|
222 @param isGlobal flag indicating a global environment |
|
223 @type bool |
|
224 @param isConda flag indicating an Anaconda virtual environment |
|
225 @type bool |
|
226 @param isRemote flag indicating a remotely accessed environment |
|
227 @type bool |
|
228 @param execPath search path string to be prepended to the PATH |
|
229 environment variable |
|
230 @type str |
|
231 """ |
|
232 if venvName in self.__virtualEnvironments: |
|
233 ok = E5MessageBox.yesNo( |
|
234 None, |
|
235 self.tr("Add Virtual Environment"), |
|
236 self.tr("""A virtual environment named <b>{0}</b> exists""" |
|
237 """ already. Shall it be replaced?""") |
|
238 .format(venvName), |
|
239 icon=E5MessageBox.Warning) |
|
240 if not ok: |
|
241 from .VirtualenvNameDialog import VirtualenvNameDialog |
|
242 dlg = VirtualenvNameDialog( |
|
243 list(self.__virtualEnvironments.keys()), |
|
244 venvName) |
|
245 if dlg.exec() != QDialog.DialogCode.Accepted: |
|
246 return |
|
247 |
|
248 venvName = dlg.getName() |
|
249 |
|
250 if not venvInterpreter: |
|
251 from .VirtualenvInterpreterSelectionDialog import ( |
|
252 VirtualenvInterpreterSelectionDialog |
|
253 ) |
|
254 dlg = VirtualenvInterpreterSelectionDialog(venvName, venvDirectory) |
|
255 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
256 venvInterpreter = dlg.getData() |
|
257 |
|
258 if venvInterpreter: |
|
259 self.__virtualEnvironments[venvName] = { |
|
260 "path": venvDirectory, |
|
261 "interpreter": venvInterpreter, |
|
262 "variant": 3, # always 3 |
|
263 "is_global": isGlobal, |
|
264 "is_conda": isConda, |
|
265 "is_remote": isRemote, |
|
266 "exec_path": execPath, |
|
267 } |
|
268 |
|
269 self.__saveSettings() |
|
270 |
|
271 self.virtualEnvironmentAdded.emit() |
|
272 if self.__virtualenvManagerDialog: |
|
273 self.__virtualenvManagerDialog.refresh() |
|
274 |
|
275 def setVirtualEnv(self, venvName, venvDirectory, venvInterpreter, |
|
276 isGlobal, isConda, isRemote, execPath): |
|
277 """ |
|
278 Public method to change a virtual environment. |
|
279 |
|
280 @param venvName logical name of the virtual environment |
|
281 @type str |
|
282 @param venvDirectory directory of the virtual environment |
|
283 @type str |
|
284 @param venvInterpreter interpreter of the virtual environment |
|
285 @type str |
|
286 @param isGlobal flag indicating a global environment |
|
287 @type bool |
|
288 @param isConda flag indicating an Anaconda virtual environment |
|
289 @type bool |
|
290 @param isRemote flag indicating a remotely accessed environment |
|
291 @type bool |
|
292 @param execPath search path string to be prepended to the PATH |
|
293 environment variable |
|
294 @type str |
|
295 """ |
|
296 if venvName not in self.__virtualEnvironments: |
|
297 E5MessageBox.yesNo( |
|
298 None, |
|
299 self.tr("Change Virtual Environment"), |
|
300 self.tr("""A virtual environment named <b>{0}</b> does not""" |
|
301 """ exist. Aborting!""") |
|
302 .format(venvName), |
|
303 icon=E5MessageBox.Warning) |
|
304 return |
|
305 |
|
306 self.__virtualEnvironments[venvName] = { |
|
307 "path": venvDirectory, |
|
308 "interpreter": venvInterpreter, |
|
309 "variant": 3, # always 3 |
|
310 "is_global": isGlobal, |
|
311 "is_conda": isConda, |
|
312 "is_remote": isRemote, |
|
313 "exec_path": execPath, |
|
314 } |
|
315 |
|
316 self.__saveSettings() |
|
317 |
|
318 self.virtualEnvironmentChanged.emit(venvName) |
|
319 if self.__virtualenvManagerDialog: |
|
320 self.__virtualenvManagerDialog.refresh() |
|
321 |
|
322 def renameVirtualEnv(self, oldVenvName, venvName, venvDirectory, |
|
323 venvInterpreter, isGlobal, isConda, |
|
324 isRemote, execPath): |
|
325 """ |
|
326 Public method to substitute a virtual environment entry with a new |
|
327 name. |
|
328 |
|
329 @param oldVenvName old name of the virtual environment |
|
330 @type str |
|
331 @param venvName logical name for the virtual environment |
|
332 @type str |
|
333 @param venvDirectory directory of the virtual environment |
|
334 @type str |
|
335 @param venvInterpreter interpreter of the virtual environment |
|
336 @type str |
|
337 @param isGlobal flag indicating a global environment |
|
338 @type bool |
|
339 @param isConda flag indicating an Anaconda virtual environment |
|
340 @type bool |
|
341 @param isRemote flag indicating a remotely accessed environment |
|
342 @type bool |
|
343 @param execPath search path string to be prepended to the PATH |
|
344 environment variable |
|
345 @type str |
|
346 """ |
|
347 if oldVenvName not in self.__virtualEnvironments: |
|
348 E5MessageBox.yesNo( |
|
349 None, |
|
350 self.tr("Rename Virtual Environment"), |
|
351 self.tr("""A virtual environment named <b>{0}</b> does not""" |
|
352 """ exist. Aborting!""") |
|
353 .format(oldVenvName), |
|
354 icon=E5MessageBox.Warning) |
|
355 return |
|
356 |
|
357 del self.__virtualEnvironments[oldVenvName] |
|
358 self.addVirtualEnv(venvName, venvDirectory, venvInterpreter, |
|
359 isGlobal, isConda, isRemote, execPath) |
|
360 |
|
361 def deleteVirtualEnvs(self, venvNames): |
|
362 """ |
|
363 Public method to delete virtual environments from the list and disk. |
|
364 |
|
365 @param venvNames list of logical names for the virtual environments |
|
366 @type list of str |
|
367 """ |
|
368 venvMessages = [] |
|
369 for venvName in venvNames: |
|
370 if ( |
|
371 venvName in self.__virtualEnvironments and |
|
372 bool(self.__virtualEnvironments[venvName]["path"]) |
|
373 ): |
|
374 venvMessages.append(self.tr("{0} - {1}").format( |
|
375 venvName, self.__virtualEnvironments[venvName]["path"])) |
|
376 if venvMessages: |
|
377 from UI.DeleteFilesConfirmationDialog import ( |
|
378 DeleteFilesConfirmationDialog |
|
379 ) |
|
380 dlg = DeleteFilesConfirmationDialog( |
|
381 None, |
|
382 self.tr("Delete Virtual Environments"), |
|
383 self.tr("""Do you really want to delete these virtual""" |
|
384 """ environments?"""), |
|
385 venvMessages |
|
386 ) |
|
387 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
388 for venvName in venvNames: |
|
389 if self.__isEnvironmentDeleteable(venvName): |
|
390 if self.isCondaEnvironment(venvName): |
|
391 conda = e5App().getObject("Conda") |
|
392 path = self.__virtualEnvironments[venvName]["path"] |
|
393 res = conda.removeCondaEnvironment(prefix=path) |
|
394 if res: |
|
395 del self.__virtualEnvironments[venvName] |
|
396 else: |
|
397 shutil.rmtree( |
|
398 self.__virtualEnvironments[venvName]["path"], |
|
399 True) |
|
400 del self.__virtualEnvironments[venvName] |
|
401 |
|
402 self.__saveSettings() |
|
403 |
|
404 self.virtualEnvironmentRemoved.emit() |
|
405 if self.__virtualenvManagerDialog: |
|
406 self.__virtualenvManagerDialog.refresh() |
|
407 |
|
408 def __isEnvironmentDeleteable(self, venvName): |
|
409 """ |
|
410 Private method to check, if a virtual environment can be deleted from |
|
411 disk. |
|
412 |
|
413 @param venvName name of the virtual environment |
|
414 @type str |
|
415 @return flag indicating it can be deleted |
|
416 @rtype bool |
|
417 """ |
|
418 ok = False |
|
419 if venvName in self.__virtualEnvironments: |
|
420 ok = True |
|
421 ok &= bool(self.__virtualEnvironments[venvName]["path"]) |
|
422 ok &= not self.__virtualEnvironments[venvName]["is_global"] |
|
423 ok &= not self.__virtualEnvironments[venvName]["is_remote"] |
|
424 ok &= os.access(self.__virtualEnvironments[venvName]["path"], |
|
425 os.W_OK) |
|
426 |
|
427 return ok |
|
428 |
|
429 def removeVirtualEnvs(self, venvNames): |
|
430 """ |
|
431 Public method to delete virtual environment from the list. |
|
432 |
|
433 @param venvNames list of logical names for the virtual environments |
|
434 @type list of str |
|
435 """ |
|
436 venvMessages = [] |
|
437 for venvName in venvNames: |
|
438 if venvName in self.__virtualEnvironments: |
|
439 venvMessages.append(self.tr("{0} - {1}").format( |
|
440 venvName, self.__virtualEnvironments[venvName]["path"])) |
|
441 if venvMessages: |
|
442 from UI.DeleteFilesConfirmationDialog import ( |
|
443 DeleteFilesConfirmationDialog |
|
444 ) |
|
445 dlg = DeleteFilesConfirmationDialog( |
|
446 None, |
|
447 self.tr("Remove Virtual Environments"), |
|
448 self.tr("""Do you really want to remove these virtual""" |
|
449 """ environments?"""), |
|
450 venvMessages |
|
451 ) |
|
452 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
453 for venvName in venvNames: |
|
454 if venvName in self.__virtualEnvironments: |
|
455 del self.__virtualEnvironments[venvName] |
|
456 |
|
457 self.__saveSettings() |
|
458 |
|
459 self.virtualEnvironmentRemoved.emit() |
|
460 if self.__virtualenvManagerDialog: |
|
461 self.__virtualenvManagerDialog.refresh() |
|
462 |
|
463 def getEnvironmentEntries(self): |
|
464 """ |
|
465 Public method to get a dictionary containing the defined virtual |
|
466 environment entries. |
|
467 |
|
468 @return dictionary containing a copy of the defined virtual |
|
469 environments |
|
470 @rtype dict |
|
471 """ |
|
472 return copy.deepcopy(self.__virtualEnvironments) |
|
473 |
|
474 @pyqtSlot() |
|
475 def showVirtualenvManagerDialog(self, modal=False): |
|
476 """ |
|
477 Public slot to show the virtual environment manager dialog. |
|
478 |
|
479 @param modal flag indicating that the dialog should be shown in |
|
480 a blocking mode |
|
481 """ |
|
482 if self.__virtualenvManagerDialog is None: |
|
483 from .VirtualenvManagerDialog import VirtualenvManagerDialog |
|
484 self.__virtualenvManagerDialog = VirtualenvManagerDialog( |
|
485 self, self.__ui) |
|
486 |
|
487 if modal: |
|
488 self.__virtualenvManagerDialog.exec() |
|
489 else: |
|
490 self.__virtualenvManagerDialog.show() |
|
491 |
|
492 def shutdown(self): |
|
493 """ |
|
494 Public method to shutdown the manager. |
|
495 """ |
|
496 if self.__virtualenvManagerDialog is not None: |
|
497 self.__virtualenvManagerDialog.close() |
|
498 self.__virtualenvManagerDialog = None |
|
499 |
|
500 def isUnique(self, venvName): |
|
501 """ |
|
502 Public method to check, if the give logical name is unique. |
|
503 |
|
504 @param venvName logical name for the virtual environment |
|
505 @type str |
|
506 @return flag indicating uniqueness |
|
507 @rtype bool |
|
508 """ |
|
509 return venvName not in self.__virtualEnvironments |
|
510 |
|
511 def getVirtualenvInterpreter(self, venvName): |
|
512 """ |
|
513 Public method to get the interpreter for a virtual environment. |
|
514 |
|
515 @param venvName logical name for the virtual environment |
|
516 @type str |
|
517 @return interpreter path |
|
518 @rtype str |
|
519 """ |
|
520 if venvName in self.__virtualEnvironments: |
|
521 return self.__virtualEnvironments[venvName]["interpreter"] |
|
522 else: |
|
523 return "" |
|
524 |
|
525 def getVirtualenvDirectory(self, venvName): |
|
526 """ |
|
527 Public method to get the directory of a virtual environment. |
|
528 |
|
529 @param venvName logical name for the virtual environment |
|
530 @type str |
|
531 @return directory path |
|
532 @rtype str |
|
533 """ |
|
534 if venvName in self.__virtualEnvironments: |
|
535 return self.__virtualEnvironments[venvName]["path"] |
|
536 else: |
|
537 return "" |
|
538 |
|
539 def getVirtualenvNames(self, noRemote=False, noConda=False): |
|
540 """ |
|
541 Public method to get a list of defined virtual environments. |
|
542 |
|
543 @param noRemote flag indicating to exclude environments for remote |
|
544 debugging |
|
545 @type bool |
|
546 @param noConda flag indicating to exclude Conda environments |
|
547 @type bool |
|
548 @return list of defined virtual environments |
|
549 @rtype list of str |
|
550 """ |
|
551 environments = list(self.__virtualEnvironments.keys()) |
|
552 if noRemote: |
|
553 environments = [name for name in environments |
|
554 if not self.isRemoteEnvironment(name)] |
|
555 if noConda: |
|
556 environments = [name for name in environments |
|
557 if not self.isCondaEnvironment(name)] |
|
558 |
|
559 return environments |
|
560 |
|
561 def isGlobalEnvironment(self, venvName): |
|
562 """ |
|
563 Public method to test, if a given environment is a global one. |
|
564 |
|
565 @param venvName logical name of the virtual environment |
|
566 @type str |
|
567 @return flag indicating a global environment |
|
568 @rtype bool |
|
569 """ |
|
570 if venvName in self.__virtualEnvironments: |
|
571 return self.__virtualEnvironments[venvName]["is_global"] |
|
572 else: |
|
573 return False |
|
574 |
|
575 def isCondaEnvironment(self, venvName): |
|
576 """ |
|
577 Public method to test, if a given environment is an Anaconda |
|
578 environment. |
|
579 |
|
580 @param venvName logical name of the virtual environment |
|
581 @type str |
|
582 @return flag indicating an Anaconda environment |
|
583 @rtype bool |
|
584 """ |
|
585 if venvName in self.__virtualEnvironments: |
|
586 return self.__virtualEnvironments[venvName]["is_conda"] |
|
587 else: |
|
588 return False |
|
589 |
|
590 def isRemoteEnvironment(self, venvName): |
|
591 """ |
|
592 Public method to test, if a given environment is a remotely accessed |
|
593 environment. |
|
594 |
|
595 @param venvName logical name of the virtual environment |
|
596 @type str |
|
597 @return flag indicating a remotely accessed environment |
|
598 @rtype bool |
|
599 """ |
|
600 if venvName in self.__virtualEnvironments: |
|
601 return self.__virtualEnvironments[venvName]["is_remote"] |
|
602 else: |
|
603 return False |
|
604 |
|
605 def getVirtualenvExecPath(self, venvName): |
|
606 """ |
|
607 Public method to get the search path prefix of a virtual environment. |
|
608 |
|
609 @param venvName logical name for the virtual environment |
|
610 @type str |
|
611 @return search path prefix |
|
612 @rtype str |
|
613 """ |
|
614 if venvName in self.__virtualEnvironments: |
|
615 return self.__virtualEnvironments[venvName]["exec_path"] |
|
616 else: |
|
617 return "" |
|
618 |
|
619 def setVirtualEnvironmentsBaseDir(self, baseDir): |
|
620 """ |
|
621 Public method to set the base directory for the virtual environments. |
|
622 |
|
623 @param baseDir base directory for the virtual environments |
|
624 @type str |
|
625 """ |
|
626 self.__virtualEnvironmentsBaseDir = baseDir |
|
627 self.__saveSettings() |
|
628 |
|
629 def getVirtualEnvironmentsBaseDir(self): |
|
630 """ |
|
631 Public method to set the base directory for the virtual environments. |
|
632 |
|
633 @return base directory for the virtual environments |
|
634 @rtype str |
|
635 """ |
|
636 return self.__virtualEnvironmentsBaseDir |