eric6/VirtualEnv/VirtualenvConfigurationDialog.py

changeset 6942
2602857055c5
parent 6739
110ab101766a
child 7192
a22eee00b052
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to enter the parameters for the
8 virtual environment.
9 """
10
11 from __future__ import unicode_literals
12 try:
13 str = unicode
14 except NameError:
15 pass
16
17 import os
18 import sys
19 import re
20
21 from PyQt5.QtCore import pyqtSlot, QProcess, QTimer
22 from PyQt5.QtWidgets import QDialog, QDialogButtonBox
23
24 from E5Gui.E5PathPicker import E5PathPickerModes
25
26 from .Ui_VirtualenvConfigurationDialog import Ui_VirtualenvConfigurationDialog
27
28 import Preferences
29 import Utilities
30
31 import CondaInterface
32
33
34 class VirtualenvConfigurationDialog(QDialog, Ui_VirtualenvConfigurationDialog):
35 """
36 Class implementing a dialog to enter the parameters for the
37 virtual environment.
38 """
39 def __init__(self, parent=None):
40 """
41 Constructor
42
43 @param parent reference to the parent widget
44 @type QWidget
45 """
46 super(VirtualenvConfigurationDialog, self).__init__(parent)
47 self.setupUi(self)
48
49 self.targetDirectoryPicker.setMode(E5PathPickerModes.DirectoryMode)
50 self.targetDirectoryPicker.setWindowTitle(
51 self.tr("Virtualenv Target Directory"))
52 self.targetDirectoryPicker.setDefaultDirectory(Utilities.getHomeDir())
53
54 self.extraSearchPathPicker.setMode(E5PathPickerModes.DirectoryMode)
55 self.extraSearchPathPicker.setWindowTitle(
56 self.tr("Extra Search Path for setuptools/pip"))
57 self.extraSearchPathPicker.setDefaultDirectory(Utilities.getHomeDir())
58
59 self.pythonExecPicker.setMode(E5PathPickerModes.OpenFileMode)
60 self.pythonExecPicker.setWindowTitle(
61 self.tr("Python Interpreter"))
62 self.pythonExecPicker.setDefaultDirectory(
63 sys.executable.replace("w.exe", ".exe"))
64
65 self.condaTargetDirectoryPicker.setMode(
66 E5PathPickerModes.DirectoryMode)
67 self.condaTargetDirectoryPicker.setWindowTitle(
68 self.tr("Conda Environment Location"))
69 self.condaTargetDirectoryPicker.setDefaultDirectory(
70 Utilities.getHomeDir())
71
72 self.condaCloneDirectoryPicker.setMode(
73 E5PathPickerModes.DirectoryMode)
74 self.condaCloneDirectoryPicker.setWindowTitle(
75 self.tr("Conda Environment Location"))
76 self.condaCloneDirectoryPicker.setDefaultDirectory(
77 Utilities.getHomeDir())
78
79 self.condaRequirementsFilePicker.setMode(
80 E5PathPickerModes.OpenFileMode)
81 self.condaRequirementsFilePicker.setWindowTitle(
82 self.tr("Conda Requirements File"))
83 self.condaRequirementsFilePicker.setDefaultDirectory(
84 Utilities.getHomeDir())
85 self.condaRequirementsFilePicker.setFilters(
86 self.tr("Text Files (*.txt);;All Files (*)"))
87
88 self.__versionRe = re.compile(r""".*?(\d+\.\d+\.\d+).*""")
89
90 self.__virtualenvFound = False
91 self.__pyvenvFound = False
92 self.__condaFound = False
93 self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
94
95 self.__mandatoryStyleSheet = "QLineEdit {border: 2px solid;}"
96 self.targetDirectoryPicker.setStyleSheet(self.__mandatoryStyleSheet)
97 self.nameEdit.setStyleSheet(self.__mandatoryStyleSheet)
98 self.condaTargetDirectoryPicker.setStyleSheet(
99 self.__mandatoryStyleSheet)
100 self.condaNameEdit.setStyleSheet(self.__mandatoryStyleSheet)
101
102 self.__setVirtualenvVersion()
103 self.__setPyvenvVersion()
104 self.__setCondaVersion()
105 if self.__pyvenvFound:
106 self.pyvenvButton.setChecked(True)
107 elif self.__virtualenvFound:
108 self.virtualenvButton.setChecked(True)
109 elif self.__condaFound:
110 self.condaButton.setChecked(True)
111
112 self.condaInsecureCheckBox.setEnabled(
113 CondaInterface.condaVersion() >= (4, 3, 18))
114
115 msh = self.minimumSizeHint()
116 self.resize(max(self.width(), msh.width()), msh.height())
117
118 def __updateOK(self):
119 """
120 Private method to update the enabled status of the OK button.
121 """
122 if self.virtualenvButton.isChecked() or self.pyvenvButton.isChecked():
123 self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(
124 (self.__virtualenvFound or self.__pyvenvFound) and
125 bool(self.targetDirectoryPicker.text()) and
126 bool(self.nameEdit.text())
127 )
128 elif self.condaButton.isChecked():
129 enable = bool(self.condaNameEdit.text()) or \
130 bool(self.condaTargetDirectoryPicker.text())
131 if self.condaSpecialsGroup.isChecked():
132 if self.condaCloneButton.isChecked():
133 enable &= bool(self.condaCloneNameEdit.text()) or \
134 bool(self.condaCloneDirectoryPicker.text())
135 elif self.condaRequirementsButton.isChecked():
136 enable &= bool(self.condaRequirementsFilePicker.text())
137 self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable)
138 else:
139 self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
140
141 def __updateUi(self):
142 """
143 Private method to update the UI depending on the selected
144 virtual environment creator (virtualenv or pyvenv).
145 """
146 # venv page
147 enable = self.virtualenvButton.isChecked()
148 self.extraSearchPathLabel.setEnabled(enable)
149 self.extraSearchPathPicker.setEnabled(enable)
150 self.promptPrefixLabel.setEnabled(enable)
151 self.promptPrefixEdit.setEnabled(enable)
152 self.verbosityLabel.setEnabled(enable)
153 self.verbositySpinBox.setEnabled(enable)
154 self.versionLabel.setEnabled(enable)
155 self.versionComboBox.setEnabled(enable)
156 self.unzipCheckBox.setEnabled(enable)
157 self.noSetuptoolsCheckBox.setEnabled(enable)
158 self.symlinkCheckBox.setEnabled(not enable)
159 self.upgradeCheckBox.setEnabled(not enable)
160
161 # conda page
162 enable = not self.condaSpecialsGroup.isChecked()
163 self.condaPackagesEdit.setEnabled(enable)
164 self.condaPythonEdit.setEnabled(enable)
165 self.condaInsecureCheckBox.setEnabled(
166 enable and CondaInterface.condaVersion() >= (4, 3, 18))
167 self.condaDryrunCheckBox.setEnabled(enable)
168
169 # select page
170 if self.condaButton.isChecked():
171 self.venvStack.setCurrentWidget(self.condaPage)
172 else:
173 self.venvStack.setCurrentWidget(self.venvPage)
174
175 @pyqtSlot(str)
176 def on_targetDirectoryPicker_textChanged(self, txt):
177 """
178 Private slot handling a change of the target directory.
179
180 @param txt target directory
181 @type str
182 """
183 self.__updateOK()
184
185 @pyqtSlot(str)
186 def on_pythonExecPicker_textChanged(self, txt):
187 """
188 Private slot to react to a change of the Python executable.
189
190 @param txt contents of the picker's line edit
191 @type str
192 """
193 self.__setVirtualenvVersion()
194 self.__setPyvenvVersion()
195 self.__updateOK()
196
197 @pyqtSlot(bool)
198 def on_virtualenvButton_toggled(self, checked):
199 """
200 Private slot to react to the selection of 'virtualenv'.
201
202 @param checked state of the checkbox
203 @type bool
204 """
205 self.__updateUi()
206
207 @pyqtSlot(bool)
208 def on_pyvenvButton_toggled(self, checked):
209 """
210 Private slot to react to the selection of 'pyvenv'.
211
212 @param checked state of the checkbox
213 @type bool
214 """
215 self.__updateUi()
216
217 @pyqtSlot(bool)
218 def on_condaButton_toggled(self, checked):
219 """
220 Private slot to react to the selection of 'conda'.
221
222 @param checked state of the checkbox
223 @type bool
224 """
225 self.__updateUi()
226
227 @pyqtSlot(str)
228 def on_condaNameEdit_textChanged(self, txt):
229 """
230 Private slot handling a change of the conda environment name.
231
232 @param txt environment name
233 @type str
234 """
235 self.__updateOK()
236
237 @pyqtSlot(str)
238 def on_condaTargetDirectoryPicker_textChanged(self, txt):
239 """
240 Private slot handling a change of the conda target directory.
241
242 @param txt target directory
243 @type str
244 """
245 self.__updateOK()
246
247 @pyqtSlot()
248 def on_condaSpecialsGroup_clicked(self):
249 """
250 Private slot handling the selection of the specials group.
251 """
252 self.__updateOK()
253 self.__updateUi()
254
255 @pyqtSlot(str)
256 def on_condaCloneNameEdit_textChanged(self, txt):
257 """
258 Private slot handling a change of the conda source environment name.
259
260 @param txt name of the environment to be cloned
261 @type str
262 """
263 self.__updateOK()
264
265 @pyqtSlot(str)
266 def on_condaCloneDirectoryPicker_textChanged(self, txt):
267 """
268 Private slot handling a change of the cloned from directory.
269
270 @param txt target directory
271 @type str
272 """
273 self.__updateOK()
274
275 @pyqtSlot()
276 def on_condaCloneButton_clicked(self):
277 """
278 Private slot handling the selection of the clone button.
279 """
280 self.__updateOK()
281
282 @pyqtSlot()
283 def on_condaRequirementsButton_clicked(self):
284 """
285 Private slot handling the selection of the requirements button.
286 """
287 self.__updateOK()
288
289 @pyqtSlot(str)
290 def on_condaRequirementsFilePicker_textChanged(self, txt):
291 """
292 Private slot handling a change of the requirements file entry.
293
294 @param txt current text of the requirements file entry
295 @type str
296 """
297 self.__updateOK()
298
299 def __setVirtualenvVersion(self):
300 """
301 Private method to determine the virtualenv version and set the
302 respective label.
303 """
304 calls = [
305 (sys.executable.replace("w.exe", ".exe"),
306 ["-m", "virtualenv", "--version"]),
307 ("virtualenv", ["--version"]),
308 ]
309 if self.pythonExecPicker.text():
310 calls.append((self.pythonExecPicker.text(),
311 ["-m", "virtualenv", "--version"]))
312
313 proc = QProcess()
314 for prog, args in calls:
315 proc.start(prog, args)
316
317 if not proc.waitForStarted(5000):
318 # try next entry
319 continue
320
321 if not proc.waitForFinished(5000):
322 # process hangs, kill it
323 QTimer.singleShot(2000, proc.kill)
324 proc.waitForFinished(3000)
325 version = self.tr('<virtualenv did not finish within 5s.>')
326 self.__virtualenvFound = False
327 break
328
329 if proc.exitCode() != 0:
330 # returned with error code, try next
331 continue
332
333 output = str(proc.readAllStandardOutput(),
334 Preferences.getSystem("IOEncoding"),
335 'replace').strip()
336 match = re.match(self.__versionRe, output)
337 if match:
338 self.__virtualenvFound = True
339 version = match.group(1)
340 break
341 else:
342 self.__virtualenvFound = False
343 version = self.tr('<No suitable virtualenv found.>')
344
345 self.virtualenvButton.setText(self.tr(
346 "virtualenv Version: {0}".format(version)))
347 self.virtualenvButton.setEnabled(self.__virtualenvFound)
348 if not self.__virtualenvFound:
349 self.virtualenvButton.setChecked(False)
350
351 def __setPyvenvVersion(self):
352 """
353 Private method to determine the pyvenv version and set the respective
354 label.
355 """
356 calls = []
357 if self.pythonExecPicker.text():
358 calls.append((self.pythonExecPicker.text(),
359 ["-m", "venv"]))
360 calls.extend([
361 (sys.executable.replace("w.exe", ".exe"),
362 ["-m", "venv"]),
363 ("python3", ["-m", "venv"]),
364 ("python", ["-m", "venv"]),
365 ])
366
367 proc = QProcess()
368 for prog, args in calls:
369 proc.start(prog, args)
370
371 if not proc.waitForStarted(5000):
372 # try next entry
373 continue
374
375 if not proc.waitForFinished(5000):
376 # process hangs, kill it
377 QTimer.singleShot(2000, proc.kill)
378 proc.waitForFinished(3000)
379 version = self.tr('<pyvenv did not finish within 5s.>')
380 self.__pyvenvFound = False
381 break
382
383 if proc.exitCode() not in [0, 2]:
384 # returned with error code, try next
385 continue
386
387 proc.start(prog, ["--version"])
388 proc.waitForFinished(5000)
389 output = str(proc.readAllStandardOutput(),
390 Preferences.getSystem("IOEncoding"),
391 'replace').strip()
392 match = re.match(self.__versionRe, output)
393 if match:
394 self.__pyvenvFound = True
395 version = match.group(1)
396 break
397 else:
398 self.__pyvenvFound = False
399 version = self.tr('<No suitable pyvenv found.>')
400
401 self.pyvenvButton.setText(self.tr(
402 "pyvenv Version: {0}".format(version)))
403 self.pyvenvButton.setEnabled(self.__pyvenvFound)
404 if not self.__pyvenvFound:
405 self.pyvenvButton.setChecked(False)
406
407 def __setCondaVersion(self):
408 """
409 Private method to determine the conda version and set the respective
410 label.
411 """
412 self.__condaFound = bool(CondaInterface.condaVersion())
413 self.condaButton.setText(self.tr(
414 "conda Version: {0}".format(CondaInterface.condaVersionStr())))
415 self.condaButton.setEnabled(self.__condaFound)
416 if not self.__condaFound:
417 self.condaButton.setChecked(False)
418
419 def __generateTargetDir(self):
420 """
421 Private method to generate a valid target directory path.
422
423 @return target directory path
424 @rtype str
425 """
426 targetDirectory = Utilities.toNativeSeparators(
427 self.targetDirectoryPicker.text())
428 if not os.path.isabs(targetDirectory):
429 targetDirectory = os.path.join(os.path.expanduser("~"),
430 targetDirectory)
431 return targetDirectory
432
433 def __generateArguments(self):
434 """
435 Private method to generate the process arguments.
436
437 @return process arguments
438 @rtype list of str
439 """
440 args = []
441 if self.condaButton.isChecked():
442 if bool(self.condaNameEdit.text()):
443 args.extend(["--name", self.condaNameEdit.text()])
444 if bool(self.condaTargetDirectoryPicker.text()):
445 args.extend(["--prefix",
446 self.condaTargetDirectoryPicker.text()])
447 if self.condaSpecialsGroup.isChecked():
448 if self.condaCloneButton.isChecked():
449 if bool(self.condaCloneNameEdit.text()):
450 args.extend(
451 ["--clone", self.condaCloneNameEdit.text()]
452 )
453 elif bool(self.condaCloneDirectoryPicker.text()):
454 args.extend(["--clone",
455 self.condaCloneDirectoryPicker.text()])
456 elif self.condaRequirementsButton.isChecked():
457 args.extend(
458 ["--file", self.condaRequirementsFilePicker.text()]
459 )
460 if self.condaInsecureCheckBox.isChecked():
461 args.append("--insecure")
462 if self.condaDryrunCheckBox.isChecked():
463 args.append("--dry-run")
464 if not self.condaSpecialsGroup.isChecked():
465 if bool(self.condaPythonEdit.text()):
466 args.append("python={0}".format(
467 self.condaPythonEdit.text()))
468 if bool(self.condaPackagesEdit.text()):
469 args.extend(self.condaPackagesEdit.text().split())
470 else:
471 if self.virtualenvButton.isChecked():
472 if self.extraSearchPathPicker.text():
473 args.append("--extra-search-dir={0}".format(
474 Utilities.toNativeSeparators(
475 self.extraSearchPathPicker.text())))
476 if self.promptPrefixEdit.text():
477 args.append("--prompt={0}".format(
478 self.promptPrefixEdit.text().replace(" ", "_")))
479 if self.pythonExecPicker.text():
480 args.append("--python={0}".format(
481 Utilities.toNativeSeparators(
482 self.pythonExecPicker.text())))
483 elif self.versionComboBox.currentText():
484 args.append("--python=python{0}".format(
485 self.versionComboBox.currentText()))
486 if self.verbositySpinBox.value() == 1:
487 args.append("--verbose")
488 elif self.verbositySpinBox.value() == -1:
489 args.append("--quiet")
490 if self.clearCheckBox.isChecked():
491 args.append("--clear")
492 if self.systemCheckBox.isChecked():
493 args.append("--system-site-packages")
494 if self.unzipCheckBox.isChecked():
495 args.append("--unzip-setuptools")
496 if self.noSetuptoolsCheckBox.isChecked():
497 args.append("--no-setuptools")
498 if self.noPipCcheckBox.isChecked():
499 args.append("--no-pip")
500 if self.copyCheckBox.isChecked():
501 args.append("--always-copy")
502 elif self.pyvenvButton.isChecked():
503 if self.clearCheckBox.isChecked():
504 args.append("--clear")
505 if self.systemCheckBox.isChecked():
506 args.append("--system-site-packages")
507 if self.noPipCcheckBox.isChecked():
508 args.append("--without-pip")
509 if self.copyCheckBox.isChecked():
510 args.append("--copies")
511 if self.symlinkCheckBox.isChecked():
512 args.append("--symlinks")
513 if self.upgradeCheckBox.isChecked():
514 args.append("--upgrade")
515 targetDirectory = self.__generateTargetDir()
516 args.append(targetDirectory)
517
518 return args
519
520 def getData(self):
521 """
522 Public method to retrieve the dialog data.
523
524 @return dictionary containing the data for the two environment
525 variants. The keys for both variants are 'arguments' containing the
526 command line arguments, 'logicalName' containing the environment
527 name to be used with the virtual env manager and 'envType'
528 containing the environment type (virtualenv, pyvenv or conda). The
529 virtualenv/pyvenv specific keys are 'openTarget' containg a flag to
530 open the target directory after creation, 'createLog' containing a
531 flag to write a log file, 'createScript' containing a flag to write
532 a script, 'targetDirectory' containing the target directory and
533 'pythonExe' containing the Python interpreter to be used. The
534 conda specific key is 'command' giving the conda command to be
535 executed (always 'create').
536 @rtype dict
537 """
538 args = self.__generateArguments()
539 resultDict = {
540 "arguments": args,
541 "logicalName": self.nameEdit.text(),
542 }
543 if self.condaButton.isChecked():
544 resultDict.update({
545 "envType": "conda",
546 "command": "create",
547 })
548 else:
549 resultDict.update({
550 "envType": ("pyvenv" if self.pyvenvButton.isChecked() else
551 "virtualenv"),
552 "openTarget": self.openCheckBox.isChecked(),
553 "createLog": self.logCheckBox.isChecked(),
554 "createScript": self.scriptCheckBox.isChecked(),
555 "targetDirectory": self.__generateTargetDir(),
556 "pythonExe": Utilities.toNativeSeparators(
557 self.pythonExecPicker.text()),
558 })
559
560 return resultDict

eric ide

mercurial