src/eric7/VirtualEnv/VirtualenvConfigurationDialog.py

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

eric ide

mercurial