Sun, 27 Jan 2019 19:52:37 +0100
Continued implementing environment creation with conda.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaExecDialog.py Sun Jan 27 19:52:37 2019 +0100 @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to show the output of a conda execution. +""" + +from __future__ import unicode_literals +try: + str = unicode +except NameError: + pass + +import json + +from PyQt5.QtCore import pyqtSlot, QProcess, QTimer +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QAbstractButton + +from E5Gui import E5MessageBox + +from .Ui_CondaExecDialog import Ui_CondaExecDialog + +import Preferences + + +class CondaExecDialog(QDialog, Ui_CondaExecDialog): + """ + Class documentation goes here. + """ + def __init__(self, configuration, venvManager, parent=None): + """ + Constructor + + @param parent reference to the parent widget + @type QWidget + """ + super(CondaExecDialog, self).__init__(parent) + self.setupUi(self) + + self.__venvName = configuration["logicalName"] + self.__venvManager = venvManager + + self.__process = None + self.__condaExe = Preferences.getConda("CondaExecutable") + if not self.__condaExe: + self.__condaExe = "conda" + + @pyqtSlot(QAbstractButton) + def on_buttonBox_clicked(self, button): + """ + Private slot called by a button of the button box clicked. + + @param button button that was clicked + @type QAbstractButton + """ + if button == self.buttonBox.button(QDialogButtonBox.Close): + self.accept() + elif button == self.buttonBox.button(QDialogButtonBox.Cancel): + self.__finish() + + def start(self, arguments): + """ + Public slot to start the conda command. + + @param arguments commandline arguments for conda program + @type list of str + """ + self.errorGroup.hide() + self.progressLabel.hide() + self.progressBar.hide() + + self.contents.clear() + self.errors.clear() + self.progressLabel.clear() + self.progressBar.setValue(0) + + self.__bufferedStdout = None + self.__json = "--json" in arguments + self.__firstProgress = True + + self.__process = QProcess() + self.__process.readyReadStandardOutput.connect(self.__readStdout) + self.__process.readyReadStandardError.connect(self.__readStderr) + self.__process.finished.connect(self.__finish) + + self.__process.start(self.__condaExe, arguments) + procStarted = self.__process.waitForStarted(5000) + if not procStarted: + E5MessageBox.critical( + self, + self.tr("Conda Execution"), + self.tr("""The conda executable could not be started. Is it""" + """ configured correctly?""")) + self.__finish(1, 0) + else: + self.__logOutput(self.tr("Operation started.\n")) + + def __finish(self, exitCode, exitStatus, giveUp=False): + """ + Private slot called when the process finished. + + It is called when the process finished or + the user pressed the button. + + @param exitCode exit code of the process (integer) + @param exitStatus exit status of the process (QProcess.ExitStatus) + @keyparam giveUp flag indicating to not start another attempt (boolean) + """ + if self.__process is not None and \ + self.__process.state() != QProcess.NotRunning: + self.__process.terminate() + QTimer.singleShot(2000, self.__process.kill) + self.__process.waitForFinished(3000) + + self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True) + self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False) + self.buttonBox.button(QDialogButtonBox.Close).setDefault(True) + + self.__logOutput(self.tr("Operation finished.\n")) + if self.__json: + if self.__bufferedStdout: + try: + jsonDict = json.loads(self.__bufferedStdout) + except Exception as error: + self.__logError(str(error)) + return + + if "success" in jsonDict and jsonDict["success"]: + if "prefix" in jsonDict: + prefix = jsonDict["prefix"] + elif "dst_prefix" in jsonDict: + prefix = jsonDict["dst_prefix"] + else: + prefix = "" + self.__venvManager.addVirtualEnv(self.__venvName, + prefix, + isConda=True) + + def __progressLabelString(self, text): + """ + Private method to process a string and format it for the progress + label. + + @param text text to be processed + @type str + @return formatted progress label string + @rtype str + """ + parts = text.split("|") + return self.tr("{0} (Size: {1})".format(parts[0].strip(), + parts[1].strip())) + + def __readStdout(self): + """ + Private slot to handle the readyReadStandardOutput signal. + + It reads the output of the process, formats it and inserts it into + the contents pane. + """ + all_stdout = str(self.__process.readAllStandardOutput(), + Preferences.getSystem("IOEncoding"), + 'replace') + all_stdout = all_stdout.replace("\x00", "") + if self.__json: + for stdout in all_stdout.splitlines(): + try: + jsonDict = json.loads(stdout.replace("\x00", "").strip()) + if "progress" in jsonDict: + self.progressLabel.setText( + self.__progressLabelString(jsonDict["fetch"])) + self.progressBar.setValue( + int(jsonDict["progress"] * 100)) + if self.__firstProgress: + self.progressLabel.show() + self.progressBar.show() + self.__firstProgress = False + else: + if self.__bufferedStdout is None: + self.__bufferedStdout = stdout + else: + self.__bufferedStdout += stdout + except (TypeError, ValueError): + if self.__bufferedStdout is None: + self.__bufferedStdout = stdout + else: + self.__bufferedStdout += stdout + else: + self.__logOutput(all_stdout) + + def __readStderr(self): + """ + Private slot to handle the readyReadStandardError signal. + + It reads the error output of the process and inserts it into the + error pane. + """ + self.__process.setReadChannel(QProcess.StandardError) + + while self.__process.canReadLine(): + stderr = str(self.__process.readLine(), + Preferences.getSystem("IOEncoding"), + 'replace') + self.__logError(stderr) + + def __logOutput(self, stdout): + """ + Private method to log some output. + + @param stdout output string to log + @type str + """ + self.contents.insertPlainText(stdout) + self.contents.ensureCursorVisible() + + def __logError(self, stderr): + """ + Private method to log an error. + + @param stderr error string to log + @type str + """ + self.errorGroup.show() + self.errors.insertPlainText(stderr) + self.errors.ensureCursorVisible()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/CondaExecDialog.ui Sun Jan 27 19:52:37 2019 +0100 @@ -0,0 +1,135 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CondaExecDialog</class> + <widget class="QDialog" name="CondaExecDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>750</width> + <height>600</height> + </rect> + </property> + <property name="windowTitle"> + <string>Conda Execution</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QGroupBox" name="messagesGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>3</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Messages</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTextBrowser" name="contents"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>3</verstretch> + </sizepolicy> + </property> + <property name="whatsThis"> + <string><b>virtualenv Execution</b> +<p>This shows the output of the virtualenv command.</p></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QLabel" name="progressLabel"/> + </item> + <item> + <widget class="QProgressBar" name="progressBar"/> + </item> + <item> + <widget class="QGroupBox" name="errorGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Errors</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QTextBrowser" name="errors"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="whatsThis"> + <string><b>virtualenv Execution</b> +<p>This shows the errors of the virtualenv command.</p></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>contents</tabstop> + <tabstop>errors</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CondaExecDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>CondaExecDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CondaInterface/__init__.py Sun Jan 27 19:52:37 2019 +0100 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing the various conda related modules. +"""
--- a/VirtualEnv/VirtualenvConfigurationDialog.py Sun Jan 27 12:39:53 2019 +0100 +++ b/VirtualEnv/VirtualenvConfigurationDialog.py Sun Jan 27 19:52:37 2019 +0100 @@ -417,8 +417,28 @@ """ args = [] if self.condaButton.isChecked(): - # TODO: assemble the conda arguments - pass + args.extend(["create", "--json", "--yes"]) + if bool(self.condaNameEdit.text()): + args.extend(["--name", self.condaNameEdit.text()]) + if bool(self.condaTargetDirectoryPicker.text()): + args.extend(["--prefix", + self.condaTargetDirectoryPicker.text()]) + if self.condaCloneGroup.isChecked(): + if bool(self.condaCloneNameEdit.text()): + args.extend(["--clone", self.condaCloneNameEdit.text()]) + elif bool(self.condaCloneDirectoryPicker.text()): + args.extend(["--clone", + self.condaCloneDirectoryPicker.text()]) + if self.condaInsecureCheckBox.isChecked(): + args.append("--insecure") + if self.condaDryrunCheckBox.isChecked(): + args.append("--dry-run") + if not self.condaCloneGroup.isChecked(): + if bool(self.condaPythonEdit.text()): + args.append("python={0}".format( + self.condaPythonEdit.text())) + if bool(self.condaPackagesEdit.text()): + args.extend(self.condaPackagesEdit.text().split()) else: if self.virtualenvButton.isChecked(): if self.extraSearchPathPicker.text():
--- a/VirtualEnv/VirtualenvExecDialog.py Sun Jan 27 12:39:53 2019 +0100 +++ b/VirtualEnv/VirtualenvExecDialog.py Sun Jan 27 19:52:37 2019 +0100 @@ -130,7 +130,7 @@ if button == self.buttonBox.button(QDialogButtonBox.Close): self.accept() elif button == self.buttonBox.button(QDialogButtonBox.Cancel): - self.__finish() + self.__finish(0, 0, ) def __finish(self, exitCode, exitStatus, giveUp=False): """ @@ -236,7 +236,7 @@ """ Private method to log some output. - @param s output sstring to log (string) + @param s output string to log (string) """ self.contents.insertPlainText(s) self.contents.ensureCursorVisible()
--- a/VirtualEnv/VirtualenvManager.py Sun Jan 27 12:39:53 2019 +0100 +++ b/VirtualEnv/VirtualenvManager.py Sun Jan 27 19:52:37 2019 +0100 @@ -152,8 +152,8 @@ ## targetDir, interpreter) = dlg.getData() if resultDict["envType"] == "conda": - # TODO: add call to the conda exec dialog - pass + from CondaInterface.CondaExecDialog import CondaExecDialog + dia = CondaExecDialog(resultDict, self) else: # now do the call from .VirtualenvExecDialog import VirtualenvExecDialog @@ -193,7 +193,14 @@ .format(venvName), icon=E5MessageBox.Warning) if not ok: - return + from .VirtualenvNameDialog import VirtualenvNameDialog + dlg = VirtualenvNameDialog( + list(self.__virtualEnvironments.keys()), + venvName) + if dlg.exec_() != QDialog.Accepted: + return + + venvName = dlg.getName() if not venvInterpreter: from .VirtualenvInterpreterSelectionDialog import \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualEnv/VirtualenvNameDialog.py Sun Jan 27 19:52:37 2019 +0100 @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2019 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to enter the logical name for a new virtual +environment. +""" + +from PyQt5.QtCore import pyqtSlot, Qt +from PyQt5.QtWidgets import QDialog, QDialogButtonBox + +from .Ui_VirtualenvNameDialog import Ui_VirtualenvNameDialog + + +class VirtualenvNameDialog(QDialog, Ui_VirtualenvNameDialog): + """ + Class implementing a dialog to enter the logical name for a new virtual + environment. + """ + def __init__(self, environments, currentName, parent=None): + """ + Constructor + + @param environments list of environment names to be shown + @type list of str + @param currentName name to be shown in the name edit + @type str + @param parent reference to the parent widget + @type QWidget + """ + super(VirtualenvNameDialog, self).__init__(parent) + self.setupUi(self) + + self.envsList.addItems(environments) + self.nameEdit.setText(currentName) + + self.nameEdit.setFocus(Qt.OtherFocusReason) + self.nameEdit.selectAll() + + @pyqtSlot(str) + def on_nameEdit_textChanged(self, txt): + """ + Slot documentation goes here. + + @param txt contens of the name edit + @type str + """ + items = self.envsList.findItems(txt, Qt.MatchExactly) + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled( + bool(txt) and len(items) == 0) + + def getName(self): + """ + Public method to get the entered name. + + @return name for the environment + @rtype str + """ + return self.nameEdit.text()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/VirtualEnv/VirtualenvNameDialog.ui Sun Jan 27 19:52:37 2019 +0100 @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>VirtualenvNameDialog</class> + <widget class="QDialog" name="VirtualenvNameDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>450</height> + </rect> + </property> + <property name="windowTitle"> + <string>Virtualenv Name</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QListWidget" name="envsList"> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::NoSelection</enum> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Enter a logical name for the virtual environment:</string> + </property> + </widget> + </item> + <item> + <widget class="E5ClearableLineEdit" name="nameEdit"> + <property name="toolTip"> + <string>Enter a unique name for the virtual environment</string> + </property> + <property name="placeholderText"> + <string>Name for the virtual environment</string> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>E5ClearableLineEdit</class> + <extends>QLineEdit</extends> + <header>E5Gui/E5LineEdit.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>envsList</tabstop> + <tabstop>nameEdit</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>VirtualenvNameDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>VirtualenvNameDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- a/eric6.e4p Sun Jan 27 12:39:53 2019 +0100 +++ b/eric6.e4p Sun Jan 27 19:52:37 2019 +0100 @@ -16,6 +16,8 @@ <TranslationPattern>i18n/eric6_%language%.ts</TranslationPattern> <Eol index="1"/> <Sources> + <Source>CondaInterface/CondaExecDialog.py</Source> + <Source>CondaInterface/__init__.py</Source> <Source>Cooperation/ChatWidget.py</Source> <Source>Cooperation/Connection.py</Source> <Source>Cooperation/CooperationClient.py</Source> @@ -1420,6 +1422,7 @@ <Source>VirtualEnv/VirtualenvInterpreterSelectionDialog.py</Source> <Source>VirtualEnv/VirtualenvManager.py</Source> <Source>VirtualEnv/VirtualenvManagerDialog.py</Source> + <Source>VirtualEnv/VirtualenvNameDialog.py</Source> <Source>VirtualEnv/__init__.py</Source> <Source>WebBrowser/AdBlock/AdBlockDialog.py</Source> <Source>WebBrowser/AdBlock/AdBlockExceptionsDialog.py</Source> @@ -1710,6 +1713,7 @@ <Source>uninstall.py</Source> </Sources> <Forms> + <Form>CondaInterface/CondaExecDialog.ui</Form> <Form>Cooperation/ChatWidget.ui</Form> <Form>DataViews/CodeMetricsDialog.ui</Form> <Form>DataViews/PyCoverageDialog.ui</Form> @@ -2142,6 +2146,7 @@ <Form>VirtualEnv/VirtualenvExecDialog.ui</Form> <Form>VirtualEnv/VirtualenvInterpreterSelectionDialog.ui</Form> <Form>VirtualEnv/VirtualenvManagerDialog.ui</Form> + <Form>VirtualEnv/VirtualenvNameDialog.ui</Form> <Form>WebBrowser/AdBlock/AdBlockDialog.ui</Form> <Form>WebBrowser/AdBlock/AdBlockExceptionsDialog.ui</Form> <Form>WebBrowser/Bookmarks/AddBookmarkDialog.ui</Form>