Skip to content

Commit 573016c

Browse files
committed
Use QMessageBox to prompt user to install spyder-kernels.
1 parent 53b9a76 commit 573016c

File tree

3 files changed

+107
-83
lines changed

3 files changed

+107
-83
lines changed

spyder/plugins/ipythonconsole/widgets/client.py

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
# Third party imports (qtpy)
2828
from qtpy.QtCore import QUrl, QTimer, Signal, Slot
29-
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
29+
from qtpy.QtWidgets import QApplication, QMessageBox, QVBoxLayout, QWidget
3030

3131
# Local imports
3232
from spyder.api.asyncdispatcher import AsyncDispatcher
@@ -43,16 +43,19 @@
4343
from spyder.utils.qthelpers import DialogManager
4444
from spyder.plugins.ipythonconsole import (
4545
SpyderKernelError,
46-
SpyderKernelVersionError,
46+
SPYDER_KERNELS_VERSION,
4747
)
4848
from spyder.plugins.ipythonconsole.utils.kernel_handler import (
4949
KernelConnectionState,
5050
KernelHandler
5151
)
5252
from spyder.plugins.ipythonconsole.widgets import ShellWidget
53+
from spyder.plugins.ipythonconsole.widgets.install_spyder_kernels import (
54+
SpyderKernelInstallWidget,
55+
INSTALL_TEXT,
56+
)
5357
from spyder.widgets.collectionseditor import CollectionsEditor
5458
from spyder.widgets.mixins import SaveHistoryMixin
55-
from spyder.plugins.ipythonconsole.widgets import SpyderKernelInstallWidget
5659

5760
if typing.TYPE_CHECKING:
5861
from spyder.plugins.remoteclient.api.modules import JupyterAPI
@@ -175,7 +178,8 @@ def __init__(
175178
self.infowidget = self.container.infowidget
176179
self.blank_page = self._create_blank_page()
177180
self.kernel_loading_page = self._create_loading_page()
178-
self.env_loading_page = self._create_loading_page(env=True)
181+
self.env_loading_page = self._create_loading_page(kind="env")
182+
self.spyk_installing_page = self._create_loading_page(kind="install")
179183

180184
if self.is_remote():
181185
# Keep a reference
@@ -207,6 +211,15 @@ def __init__(
207211
self.__remote_restart_requested = False
208212
self.__remote_reconnect_requested = False
209213

214+
# --- Install spyder-kernels message box
215+
self.install_mbox = QMessageBox(self)
216+
self.install_mbox.setIcon(QMessageBox.Icon.Question)
217+
self.install_mbox.setWindowTitle(self.container._plugin.get_name())
218+
self.install_mbox.setStandardButtons(
219+
QMessageBox.Yes | QMessageBox.Cancel
220+
)
221+
self.install_mbox.button(QMessageBox.Yes).setText(_("Install"))
222+
210223
# ---- Private methods
211224
# -------------------------------------------------------------------------
212225
def _when_kernel_is_ready(self):
@@ -245,16 +258,20 @@ def _when_kernel_is_ready(self):
245258
if self.give_focus:
246259
self.shellwidget._control.setFocus()
247260

248-
def _create_loading_page(self, env=False):
261+
def _create_loading_page(self, kind=None):
249262
"""Create html page to show while the kernel is starting"""
250263
loading_template = Template(LOADING)
251264
loading_img = get_image_path('loading_sprites')
252265
if os.name == 'nt':
253266
loading_img = loading_img.replace('\\', '/')
254-
message = _("Connecting to kernel...")
255267

256-
if env:
268+
if kind is None:
269+
message = _("Connecting to kernel...")
270+
elif kind == "env":
257271
message = _("Retrieving environment variables...")
272+
elif kind == "install":
273+
message = _("Installing spyder-kernels...")
274+
258275
page = loading_template.substitute(
259276
css_path=self.css_path,
260277
loading_img=loading_img,
@@ -385,8 +402,35 @@ def _set_initial_cwd_in_kernel(self):
385402
cwd_path, emit_cwd_change=emit_cwd_change
386403
)
387404

405+
@Slot()
406+
def _install_spyder_kernels(self, pyexec):
407+
# TODO: Disable create new client with same environment
408+
409+
# Store existing error page for reuse later, if necessary
410+
self.installwidget.info_page = self.info_page
411+
412+
self._show_loading_page(self.spyk_installing_page)
413+
self.installwidget.show()
414+
415+
# Install spyder kernels...
416+
self.installwidget.install_spyder_kernels(pyexec)
417+
388418
# ---- Public API
389419
# -------------------------------------------------------------------------
420+
def show_install_mbox(self, pyexec):
421+
self.install_mbox.setText(
422+
INSTALL_TEXT.format(
423+
SPYDER_KERNELS_VERSION.replace(">", ">").replace(
424+
"<", "&lt;"
425+
),
426+
pyexec,
427+
),
428+
)
429+
self.install_mbox.accepted.connect(
430+
functools.partial(self._install_spyder_kernels, pyexec)
431+
)
432+
self.install_mbox.show()
433+
390434
@property
391435
def connection_file(self):
392436
if self.kernel_handler is None:
@@ -517,22 +561,29 @@ def stop_button_click_handler(self):
517561
else:
518562
self.shellwidget.pdb_execute_command('exit')
519563

520-
def show_kernel_install(self):
521-
self._hide_loading_page()
522-
self.shellwidget.hide()
523-
self.installwidget.show()
524-
525-
def connect_after_kernel_install(self):
526-
self._show_loading_page(self.env_loading_page)
527-
self.sig_connect_after_kernel_install.emit()
564+
def process_kernel_install(self, exit_code, exit_status, output=None):
565+
# TODO: Re-enable create new client with same environment
566+
567+
if exit_code == 0 and exit_status == 0:
568+
# Success!
569+
self._show_loading_page(self.env_loading_page)
570+
self.sig_connect_after_kernel_install.emit()
571+
elif exit_code == 15 and exit_status == 1:
572+
# Cancelled by user, just display previous kernel error page
573+
self._show_loading_page(self.installwidget.info_page)
574+
elif exit_code != 0 and exit_status == 0:
575+
# An error occurred during install
576+
self.show_kernel_error(f"<tt>{output}</tt>", install=True)
577+
else:
578+
# Unknown error
579+
logger.info(
580+
"Unknown installer error. "
581+
f"Exit code: {exit_code}; exit status: {exit_status}"
582+
)
528583

529-
def show_kernel_error(self, error):
584+
def show_kernel_error(self, error, install=False):
530585
"""Show kernel initialization errors in infowidget."""
531-
if isinstance(error, SpyderKernelVersionError):
532-
self.installwidget.kernel_error = error
533-
self.show_kernel_install()
534-
return
535-
elif isinstance(error, SpyderKernelError):
586+
if isinstance(error, SpyderKernelError):
536587
error = error.args[0]
537588
elif isinstance(error, Exception):
538589
error = _("The error is:<br><br>"
@@ -557,7 +608,10 @@ def show_kernel_error(self, error):
557608
error = error.replace('-', '&#8209;')
558609

559610
# Create error page
560-
message = _("An error occurred while starting the kernel")
611+
if install:
612+
message = _("An error occurred while installing spyder-kernls")
613+
else:
614+
message = _("An error occurred while starting the kernel")
561615
kernel_error_template = Template(KERNEL_ERROR)
562616
self.info_page = kernel_error_template.substitute(
563617
css_path=self.css_path,
@@ -680,6 +734,7 @@ def set_color_scheme(self, color_scheme, reset=True):
680734

681735
def close_client(self, is_last_client, close_console=False):
682736
"""Close the client."""
737+
self.install_mbox.reject()
683738
self.__on_close = lambda: None
684739
debugging = False
685740

spyder/plugins/ipythonconsole/widgets/install_spyder_kernels.py

Lines changed: 25 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from qtpy.QtGui import QTextCursor
1616
from qtpy.QtWidgets import (
1717
QDialogButtonBox,
18-
QLabel,
1918
QPlainTextEdit,
2019
QVBoxLayout,
2120
QWidget,
@@ -28,11 +27,7 @@
2827
from spyder.api.widgets.mixins import SpyderWidgetMixin
2928
from spyder.api.widgets.dialogs import SpyderDialogButtonBox
3029
from spyder.config.base import _
31-
from spyder.plugins.ipythonconsole import (
32-
SpyderKernelError,
33-
SpyderKernelVersionError,
34-
SPYDER_KERNELS_VERSION,
35-
)
30+
from spyder.plugins.ipythonconsole import SPYDER_KERNELS_VERSION
3631

3732
logger = logging.getLogger(__name__)
3833

@@ -41,6 +36,8 @@
4136
"<tt>{}</tt><br>environment in order to work with Spyder.<br><br>"
4237
"Do you want Spyder to install it for you?"
4338
)
39+
SHOW_DETAILS = _("Show details")
40+
HIDE_DETAILS = _("Hide details")
4441

4542

4643
class SpyderKernelInstallWidget(QWidget, SpyderWidgetMixin, SpyderFontsMixin):
@@ -52,14 +49,12 @@ def __init__(self, parent=None):
5249

5350
self.ipyclient = parent
5451

55-
self._kernel_error = None
52+
self.info_page = None
5653

5754
self.bbox = SpyderDialogButtonBox(
5855
QDialogButtonBox.Ok | QDialogButtonBox.Cancel
5956
)
60-
61-
self._text_label = QLabel()
62-
self._text_label.setWordWrap(True)
57+
self.bbox.button(QDialogButtonBox.Ok).setText(SHOW_DETAILS)
6358

6459
# Process to run the installation scripts
6560
self._process = QProcess(self)
@@ -69,19 +64,16 @@ def __init__(self, parent=None):
6964
lambda: self._update_details(error=True)
7065
)
7166
self._process.finished.connect(self._handle_process_finished)
72-
self._process.errorOccurred.connect(self._handle_error)
7367

7468
# Area to show stdout/stderr streams of the process that performs the
7569
# update
7670
self._streams_area = QPlainTextEdit(self)
77-
self._streams_area.setMinimumHeight(300)
7871
self._streams_area.setReadOnly(True)
7972
self._streams_area.setLineWrapMode(QPlainTextEdit.NoWrap)
8073
self._streams_area.setFont(self.get_font(SpyderFontType.Monospace))
8174
self._streams_area.hide()
8275

8376
layout = QVBoxLayout()
84-
layout.addWidget(self._text_label)
8577
layout.addWidget(self._streams_area)
8678
layout.addWidget(self.bbox)
8779
self.setLayout(layout)
@@ -92,20 +84,15 @@ def __init__(self, parent=None):
9284
# ---- Qt methods
9385
# -------------------------------------------------------------------------
9486
def accepted(self):
95-
self._text_label.hide()
96-
self.bbox.hide()
97-
self._streams_area.show()
98-
99-
# Install spyder kernels...
100-
self.install_spyder_kernels(self.kernel_error.pyexec)
87+
if self._streams_area.isVisible():
88+
self._hide_details()
89+
else:
90+
self._show_details()
10191

10292
def rejected(self):
103-
# pass through kernel error
104-
if isinstance(self.kernel_error, SpyderKernelVersionError):
105-
# Recast to avoid recursion.
106-
self.kernel_error.__class__ = SpyderKernelError
107-
108-
self.ipyclient.show_kernel_error(self.kernel_error)
93+
self._process.terminate()
94+
logger.info("Install spyder-kernels cancelled by user.")
95+
# Note: QProcess.finished is still emitted
10996

11097
# ---- Private API
11198
# -------------------------------------------------------------------------
@@ -131,54 +118,31 @@ def _update_details(self, error=False):
131118
text = str(qba.data(), "utf-8")
132119
self._add_text_to_streams_area(text)
133120

121+
def _show_details(self):
122+
self.ipyclient.infowidget.hide()
123+
self._streams_area.show()
124+
self.bbox.button(QDialogButtonBox.Ok).setText(HIDE_DETAILS)
125+
126+
def _hide_details(self):
127+
self._streams_area.hide()
128+
self.ipyclient.infowidget.show()
129+
self.bbox.button(QDialogButtonBox.Ok).setText(SHOW_DETAILS)
130+
134131
def _handle_process_finished(self, exit_code, exit_status):
132+
output = self._streams_area.toPlainText()
135133
logger.info(
136134
"Install spyder-kernels QProcess finished. "
137135
f"Exit code: {exit_code}; exit status: {exit_status}"
138136
)
139137
logger.debug(
140138
"Install spyder-kernels output:\n"
141-
f"{self._streams_area.toPlainText()}"
139+
f"{output}"
142140
)
143141

144-
if exit_code != 0:
145-
return
146-
147-
self._streams_area.hide()
148-
self._streams_area.clear()
149-
self._text_label.show()
150-
self.bbox.show()
151-
152-
self.ipyclient.connect_after_kernel_install()
153-
154-
def _handle_error(self, error):
155-
if error == QProcess.FailedToStart:
156-
text = "The process failed to start."
157-
elif error == QProcess.Crashed:
158-
text = "The process crashed."
159-
else:
160-
text = "Unknown error."
161-
162-
self._add_text_to_streams_area(text)
142+
self.ipyclient.process_kernel_install(exit_code, exit_status, output)
163143

164144
# ---- Public API
165145
# -------------------------------------------------------------------------
166-
@property
167-
def kernel_error(self):
168-
return self._kernel_error
169-
170-
@kernel_error.setter
171-
def kernel_error(self, kernel_error):
172-
self._kernel_error = kernel_error
173-
self._text_label.setText(
174-
INSTALL_TEXT.format(
175-
SPYDER_KERNELS_VERSION.replace(">", "&gt;").replace(
176-
"<", "&lt;"
177-
),
178-
kernel_error.pyexec,
179-
)
180-
)
181-
182146
def install_spyder_kernels(self, pyexec):
183147
"""Install spyder-kernels"""
184148

spyder/plugins/ipythonconsole/widgets/main_widget.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from spyder.api.widgets.main_widget import PluginMainWidget
3939
from spyder.config.base import get_home_dir, running_under_pytest
4040
from spyder.plugins.application.api import ApplicationActions
41+
from spyder.plugins.ipythonconsole import SpyderKernelVersionError
4142
from spyder.plugins.ipythonconsole.api import (
4243
ClientContextMenuActions,
4344
IPythonConsoleWidgetActions,
@@ -1440,6 +1441,10 @@ def _connect_new_client_to_kernel(
14401441
)
14411442
kernel_spec.env = future.result()
14421443
kernel_handler = self.get_cached_kernel(kernel_spec, cache=cache)
1444+
except SpyderKernelVersionError as e:
1445+
client.show_kernel_error(e)
1446+
client.show_install_mbox(e.pyexec)
1447+
return
14431448
except Exception as e:
14441449
client.show_kernel_error(e)
14451450
return

0 commit comments

Comments
 (0)