13 import sys |
13 import sys |
14 import shutil |
14 import shutil |
15 import json |
15 import json |
16 import copy |
16 import copy |
17 |
17 |
18 from PyQt5.QtCore import pyqtSlot, QObject |
18 from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject |
19 from PyQt5.QtWidgets import QDialog |
19 from PyQt5.QtWidgets import QDialog |
20 |
20 |
21 from E5Gui import E5MessageBox |
21 from E5Gui import E5MessageBox |
|
22 from E5Gui.E5Application import e5App |
22 |
23 |
23 import Preferences |
24 import Preferences |
24 import Utilities |
|
25 |
25 |
26 |
26 |
27 class VirtualenvManager(QObject): |
27 class VirtualenvManager(QObject): |
28 """ |
28 """ |
29 Class implementing an object to manage Python virtual environments. |
29 Class implementing an object to manage Python virtual environments. |
|
30 |
|
31 @signal virtualEnvironmentAdded() emitted to indicate the addition of |
|
32 a virtual environment |
|
33 @signal virtualEnvironmentRemoved() emitted to indicate the removal and |
|
34 deletion of a virtual environment |
|
35 @signal virtualEnvironmentChanged(name) emitted to indicate a change of |
|
36 a virtual environment |
30 """ |
37 """ |
31 DefaultKey = "<default>" |
38 DefaultKey = "<default>" |
|
39 |
|
40 virtualEnvironmentAdded = pyqtSignal() |
|
41 virtualEnvironmentRemoved = pyqtSignal() |
|
42 virtualEnvironmentChanged = pyqtSignal(str) |
32 |
43 |
33 def __init__(self, parent=None): |
44 def __init__(self, parent=None): |
34 """ |
45 """ |
35 Constructor |
46 Constructor |
36 |
47 |
59 # (empty for a global environment) |
70 # (empty for a global environment) |
60 # interpreter: the path of the Python interpreter |
71 # interpreter: the path of the Python interpreter |
61 # variant: Python variant (2 or 3) |
72 # variant: Python variant (2 or 3) |
62 # is_global: a flag indicating a global environment |
73 # is_global: a flag indicating a global environment |
63 # is_conda: a flag indicating an Anaconda environment |
74 # is_conda: a flag indicating an Anaconda environment |
|
75 # is_remote: a flag indicating a remotely accessed environment |
64 # exec_path: a string to be prefixed to the PATH environment |
76 # exec_path: a string to be prefixed to the PATH environment |
65 # setting |
77 # setting |
66 # |
78 # |
67 for venvName in environments: |
79 for venvName in environments: |
68 interpreter = environments[venvName]["interpreter"] |
80 environment = environments[venvName] |
69 if os.access(interpreter, os.X_OK): |
81 if ("is_remote" in environment and environment["is_remote"]) or \ |
70 environment = environments[venvName] |
82 os.access(environment["interpreter"], os.X_OK): |
71 if "is_global" not in environment: |
83 if "is_global" not in environment: |
72 environment["is_global"] = environment["path"] == "" |
84 environment["is_global"] = environment["path"] == "" |
73 if "is_conda" not in environment: |
85 if "is_conda" not in environment: |
74 environment["is_conda"] = False |
86 environment["is_conda"] = False |
|
87 if "is_remote" not in environment: |
|
88 environment["is_remote"] = False |
75 if "exec_path" not in environment: |
89 if "exec_path" not in environment: |
76 environment["exec_path"] = "" |
90 environment["exec_path"] = "" |
77 self.__virtualEnvironments[venvName] = environment |
91 self.__virtualEnvironments[venvName] = environment |
78 |
92 |
79 # check, if the interpreter used to run eric is in the environments |
93 # check, if the interpreter used to run eric is in the environments |
145 from .VirtualenvConfigurationDialog import \ |
161 from .VirtualenvConfigurationDialog import \ |
146 VirtualenvConfigurationDialog |
162 VirtualenvConfigurationDialog |
147 |
163 |
148 dlg = VirtualenvConfigurationDialog() |
164 dlg = VirtualenvConfigurationDialog() |
149 if dlg.exec_() == QDialog.Accepted: |
165 if dlg.exec_() == QDialog.Accepted: |
150 (pyvenv, args, name, openTarget, createLog, createScript, |
166 resultDict = dlg.getData() |
151 targetDir, interpreter) = dlg.getData() |
|
152 |
167 |
153 # now do the call |
168 if resultDict["envType"] == "conda": |
154 from .VirtualenvExecDialog import VirtualenvExecDialog |
169 # create the conda environment |
155 dia = VirtualenvExecDialog(pyvenv, targetDir, name, openTarget, |
170 conda = e5App().getObject("Conda") |
156 createLog, createScript, interpreter, |
171 ok, prefix, interpreter = conda.createCondaEnvironment( |
157 self) |
172 resultDict["arguments"]) |
158 dia.show() |
173 if ok and "--dry-run" not in resultDict["arguments"]: |
159 dia.start(args) |
174 self.addVirtualEnv(resultDict["logicalName"], |
160 dia.exec_() |
175 prefix, |
|
176 venvInterpreter=interpreter, |
|
177 isConda=True) |
|
178 else: |
|
179 # now do the call |
|
180 from .VirtualenvExecDialog import VirtualenvExecDialog |
|
181 dia = VirtualenvExecDialog(resultDict, self) |
|
182 dia.show() |
|
183 dia.start(resultDict["arguments"]) |
|
184 dia.exec_() |
161 |
185 |
162 def addVirtualEnv(self, venvName, venvDirectory, venvInterpreter="", |
186 def addVirtualEnv(self, venvName, venvDirectory, venvInterpreter="", |
163 venvVariant=3, isGlobal=False, isConda=False, |
187 venvVariant=3, isGlobal=False, isConda=False, |
164 execPath=""): |
188 isRemote=False, execPath=""): |
165 """ |
189 """ |
166 Public method to add a virtual environment. |
190 Public method to add a virtual environment. |
167 |
191 |
168 @param venvName logical name for the virtual environment |
192 @param venvName logical name for the virtual environment |
169 @type str |
193 @type str |
188 self.tr("""A virtual environment named <b>{0}</b> exists""" |
214 self.tr("""A virtual environment named <b>{0}</b> exists""" |
189 """ already. Shall it be replaced?""") |
215 """ already. Shall it be replaced?""") |
190 .format(venvName), |
216 .format(venvName), |
191 icon=E5MessageBox.Warning) |
217 icon=E5MessageBox.Warning) |
192 if not ok: |
218 if not ok: |
193 return |
219 from .VirtualenvNameDialog import VirtualenvNameDialog |
|
220 dlg = VirtualenvNameDialog( |
|
221 list(self.__virtualEnvironments.keys()), |
|
222 venvName) |
|
223 if dlg.exec_() != QDialog.Accepted: |
|
224 return |
|
225 |
|
226 venvName = dlg.getName() |
194 |
227 |
195 if not venvInterpreter: |
228 if not venvInterpreter: |
196 from .VirtualenvInterpreterSelectionDialog import \ |
229 from .VirtualenvInterpreterSelectionDialog import \ |
197 VirtualenvInterpreterSelectionDialog |
230 VirtualenvInterpreterSelectionDialog |
198 dlg = VirtualenvInterpreterSelectionDialog(venvName, venvDirectory) |
231 dlg = VirtualenvInterpreterSelectionDialog(venvName, venvDirectory) |
199 if dlg.exec_() == QDialog.Accepted: |
232 if dlg.exec_() == QDialog.Accepted: |
200 venvInterpreter, venvVariant = dlg.getData() |
233 venvInterpreter, venvVariant = dlg.getData() |
201 if not Utilities.startswithPath(venvInterpreter, |
|
202 venvDirectory): |
|
203 isGlobal = True |
|
204 |
234 |
205 if venvInterpreter: |
235 if venvInterpreter: |
206 self.__virtualEnvironments[venvName] = { |
236 self.__virtualEnvironments[venvName] = { |
207 "path": venvDirectory, |
237 "path": venvDirectory, |
208 "interpreter": venvInterpreter, |
238 "interpreter": venvInterpreter, |
209 "variant": venvVariant, |
239 "variant": venvVariant, |
210 "is_global": isGlobal, |
240 "is_global": isGlobal, |
211 "is_conda": isConda, |
241 "is_conda": isConda, |
|
242 "is_remote": isRemote, |
212 "exec_path": execPath, |
243 "exec_path": execPath, |
213 } |
244 } |
214 |
245 |
215 self.__saveSettings() |
246 self.__saveSettings() |
216 |
247 |
|
248 self.virtualEnvironmentAdded.emit() |
217 if self.__virtualenvManagerDialog: |
249 if self.__virtualenvManagerDialog: |
218 self.__virtualenvManagerDialog.refresh() |
250 self.__virtualenvManagerDialog.refresh() |
219 |
251 |
220 def setVirtualEnv(self, venvName, venvDirectory, venvInterpreter, |
252 def setVirtualEnv(self, venvName, venvDirectory, venvInterpreter, |
221 venvVariant, isGlobal, isConda, execPath): |
253 venvVariant, isGlobal, isConda, isRemote, |
|
254 execPath): |
222 """ |
255 """ |
223 Public method to change a virtual environment. |
256 Public method to change a virtual environment. |
224 |
257 |
225 @param venvName logical name of the virtual environment |
258 @param venvName logical name of the virtual environment |
226 @type str |
259 @type str |
252 "path": venvDirectory, |
287 "path": venvDirectory, |
253 "interpreter": venvInterpreter, |
288 "interpreter": venvInterpreter, |
254 "variant": venvVariant, |
289 "variant": venvVariant, |
255 "is_global": isGlobal, |
290 "is_global": isGlobal, |
256 "is_conda": isConda, |
291 "is_conda": isConda, |
|
292 "is_remote": isRemote, |
257 "exec_path": execPath, |
293 "exec_path": execPath, |
258 } |
294 } |
259 |
295 |
260 self.__saveSettings() |
296 self.__saveSettings() |
261 |
297 |
|
298 self.virtualEnvironmentChanged.emit(venvName) |
262 if self.__virtualenvManagerDialog: |
299 if self.__virtualenvManagerDialog: |
263 self.__virtualenvManagerDialog.refresh() |
300 self.__virtualenvManagerDialog.refresh() |
264 |
301 |
265 def renameVirtualEnv(self, oldVenvName, venvName, venvDirectory, |
302 def renameVirtualEnv(self, oldVenvName, venvName, venvDirectory, |
266 venvInterpreter, venvVariant, isGlobal, isConda, |
303 venvInterpreter, venvVariant, isGlobal, isConda, |
267 execPath): |
304 isRemote, execPath): |
268 """ |
305 """ |
269 Public method to substitute a virtual environment entry with a new |
306 Public method to substitute a virtual environment entry with a new |
270 name. |
307 name. |
271 |
308 |
272 @param oldVenvName old name of the virtual environment |
309 @param oldVenvName old name of the virtual environment |
297 icon=E5MessageBox.Warning) |
336 icon=E5MessageBox.Warning) |
298 return |
337 return |
299 |
338 |
300 del self.__virtualEnvironments[oldVenvName] |
339 del self.__virtualEnvironments[oldVenvName] |
301 self.addVirtualEnv(venvName, venvDirectory, venvInterpreter, |
340 self.addVirtualEnv(venvName, venvDirectory, venvInterpreter, |
302 venvVariant, isGlobal, isConda, execPath) |
341 venvVariant, isGlobal, isConda, isRemote, |
|
342 execPath) |
303 |
343 |
304 def deleteVirtualEnvs(self, venvNames): |
344 def deleteVirtualEnvs(self, venvNames): |
305 """ |
345 """ |
306 Public method to delete virtual environments from the list and disk. |
346 Public method to delete virtual environments from the list and disk. |
307 |
347 |
325 venvMessages |
365 venvMessages |
326 ) |
366 ) |
327 if dlg.exec_() == QDialog.Accepted: |
367 if dlg.exec_() == QDialog.Accepted: |
328 for venvName in venvNames: |
368 for venvName in venvNames: |
329 if self.__isEnvironmentDeleteable(venvName): |
369 if self.__isEnvironmentDeleteable(venvName): |
330 shutil.rmtree( |
370 if self.isCondaEnvironment(venvName): |
331 self.__virtualEnvironments[venvName]["path"], True) |
371 conda = e5App().getObject("Conda") |
332 del self.__virtualEnvironments[venvName] |
372 path = self.__virtualEnvironments[venvName]["path"] |
|
373 res = conda.removeCondaEnvironment(prefix=path) |
|
374 if res: |
|
375 del self.__virtualEnvironments[venvName] |
|
376 else: |
|
377 shutil.rmtree( |
|
378 self.__virtualEnvironments[venvName]["path"], |
|
379 True) |
|
380 del self.__virtualEnvironments[venvName] |
333 |
381 |
334 self.__saveSettings() |
382 self.__saveSettings() |
335 |
383 |
|
384 self.virtualEnvironmentRemoved.emit() |
336 if self.__virtualenvManagerDialog: |
385 if self.__virtualenvManagerDialog: |
337 self.__virtualenvManagerDialog.refresh() |
386 self.__virtualenvManagerDialog.refresh() |
338 |
387 |
339 def __isEnvironmentDeleteable(self, venvName): |
388 def __isEnvironmentDeleteable(self, venvName): |
340 """ |
389 """ |
519 if venvName in self.__virtualEnvironments: |
570 if venvName in self.__virtualEnvironments: |
520 return self.__virtualEnvironments[venvName]["is_conda"] |
571 return self.__virtualEnvironments[venvName]["is_conda"] |
521 else: |
572 else: |
522 return False |
573 return False |
523 |
574 |
|
575 def isRemoteEnvironment(self, venvName): |
|
576 """ |
|
577 Public method to test, if a given environment is a remotely accessed |
|
578 environment. |
|
579 |
|
580 @param venvName logical name of the virtual environment |
|
581 @type str |
|
582 @return flag indicating a remotely accessed environment |
|
583 @rtype bool |
|
584 """ |
|
585 if venvName in self.__virtualEnvironments: |
|
586 return self.__virtualEnvironments[venvName]["is_remote"] |
|
587 else: |
|
588 return False |
|
589 |
524 def getVirtualenvExecPath(self, venvName): |
590 def getVirtualenvExecPath(self, venvName): |
525 """ |
591 """ |
526 Public method to get the search path prefix of a virtual environment. |
592 Public method to get the search path prefix of a virtual environment. |
527 |
593 |
528 @param venvName logical name for the virtual environment |
594 @param venvName logical name for the virtual environment |