eric7/VirtualEnv/VirtualenvConfigurationDialog.py

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

eric ide

mercurial