From 994e499204330e00c1e65a88351de6421b8bc9aa Mon Sep 17 00:00:00 2001 From: "Lumberbot (aka Jack)" <39504233+meeseeksmachine@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:07:15 -0700 Subject: [PATCH] Backport PR #22501 on branch 6.x (PR: Show banner when the kernel is ready (IPython console) ) (#22535) Co-authored-by: Carlos Cordoba --- spyder/config/base.py | 2 +- .../ipythonconsole/comms/kernelcomm.py | 5 +++ .../tests/test_ipythonconsole.py | 23 +++++++---- .../plugins/ipythonconsole/widgets/shell.py | 41 +++++++++++++------ .../plugins/ipythonconsole/widgets/status.py | 2 + 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/spyder/config/base.py b/spyder/config/base.py index bd33f981654..19aa0fc2944 100644 --- a/spyder/config/base.py +++ b/spyder/config/base.py @@ -72,7 +72,7 @@ def running_in_ci_with_conda(): def running_in_binder(): """Return True if currently running in Binder.""" return ( - os.environ.get("BINDER_REPO_URL") + bool(os.environ.get("BINDER_REPO_URL")) and "spyder-ide/binder-environments" in os.environ["BINDER_REPO_URL"] ) diff --git a/spyder/plugins/ipythonconsole/comms/kernelcomm.py b/spyder/plugins/ipythonconsole/comms/kernelcomm.py index edad74e45fa..6dbfd04800a 100644 --- a/spyder/plugins/ipythonconsole/comms/kernelcomm.py +++ b/spyder/plugins/ipythonconsole/comms/kernelcomm.py @@ -104,6 +104,11 @@ def open_comm(self, kernel_client): """Open comm through the kernel client.""" self.kernel_client = kernel_client try: + logger.debug( + f"Opening kernel comm for " + f"{'<' + repr(kernel_client).split('.')[-1]}" + ) + self._register_comm( # Create new comm and send the highest protocol kernel_client.comm_manager.new_comm(self._comm_name) diff --git a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py index c6c70166bd8..931d196c2f1 100644 --- a/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py +++ b/spyder/plugins/ipythonconsole/tests/test_ipythonconsole.py @@ -52,23 +52,28 @@ def test_banners(ipyconsole, qtbot): shell = ipyconsole.get_current_shellwidget() control = shell._control - # Long banner + # Check long banner (the default) text = control.toPlainText().splitlines() - if "Update LANGUAGE_CODES" in text[0]: - text = text[1:] - while not text[0].strip(): - text = text[1:] py_ver = sys.version.splitlines()[0].strip() assert py_ver in text[0] # Python version in first line assert 'license' in text[1] # 'license' mention in second line assert '' == text[2] # Third line is empty assert ipy_release.version in text[3] # Fourth line is IPython - # Short banner - short_banner = shell.short_banner() + # Check short banner for a new console + ipyconsole.set_conf("show_banner", False) + ipyconsole.create_new_client() + shell = ipyconsole.get_current_shellwidget() + qtbot.waitUntil( + lambda: shell.spyder_kernel_ready and shell._prompt_html is not None, + timeout=SHELL_TIMEOUT + ) + py_ver = sys.version.split(' ')[0] - expected = 'Python %s -- IPython %s' % (py_ver, ipy_release.version) - assert expected == short_banner + expected = ( + f"Python {py_ver} -- IPython {ipy_release.version}\n\n" + "In [1]: " + ) + assert expected == shell._control.toPlainText() @flaky(max_runs=3) diff --git a/spyder/plugins/ipythonconsole/widgets/shell.py b/spyder/plugins/ipythonconsole/widgets/shell.py index 31f41dd84f5..3c4bae540d7 100644 --- a/spyder/plugins/ipythonconsole/widgets/shell.py +++ b/spyder/plugins/ipythonconsole/widgets/shell.py @@ -9,6 +9,7 @@ """ # Standard library imports +import logging import os import os.path as osp import time @@ -45,8 +46,11 @@ from spyder.widgets.helperwidgets import MessageCheckBox +logger = logging.getLogger(__name__) + MODULES_FAQ_URL = ( - "https://docs.spyder-ide.org/5/faq.html#using-packages-installer") + "https://docs.spyder-ide.org/5/faq.html#using-packages-installer" +) class ShellWidget(NamepaceBrowserWidget, HelpWidget, DebuggingWidget, @@ -182,6 +186,7 @@ def __init__( self._kernel_configuration = {} self.is_kernel_configured = False self._init_kernel_setup = False + self._is_banner_shown = False if handlers is None: handlers = {} @@ -199,9 +204,6 @@ def __init__( self._execute_queue = [] self.executed.connect(self.pop_execute_queue) - # To show the console banner on first prompt - self.sig_prompt_ready.connect(self._show_banner) - # Show a message in our installers to explain users how to use # modules that don't come with them. self.show_modules_message = is_conda_based_app() @@ -317,6 +319,7 @@ def handle_kernel_is_ready(self): KernelConnectionState.SpyderKernelReady ): self.setup_spyder_kernel() + self._show_banner() def handle_kernel_connection_error(self): """An error occurred when connecting to the kernel.""" @@ -796,7 +799,7 @@ def long_banner(self): ) banner = ''.join(banner_parts) - except CommError: + except (CommError, TimeoutError): banner = "" # Pylab additions @@ -834,8 +837,8 @@ def short_banner(self): env_info = self.get_pythonenv_info() py_ver = env_info['python_version'] ipy_ver = env_info['ipython_version'] - banner = f'Python {py_ver} -- IPython {ipy_ver}' - except CommError: + banner = f'Python {py_ver} -- IPython {ipy_ver}\n' + except (CommError, TimeoutError): banner = "" return banner @@ -1182,11 +1185,17 @@ def _save_clipboard_indentation(self): def _show_banner(self): """Show banner before first prompt.""" - # Don't show banner for external kernels - if self.is_external_kernel and not self.is_remote(): - return "" + if ( + # Don't show banner for external but local kernels + self.is_external_kernel and not self.is_remote() + # Don't show it if it was already shown + or self._is_banner_shown + ): + return + + logger.debug(f"Showing banner for {self}") - # Detect what kind of banner we want to show + # Check what kind of banner we want to show show_banner_o = self.additional_options['show_banner'] if show_banner_o: banner = self.long_banner() @@ -1199,7 +1208,7 @@ def _show_banner(self): self._insert_plain_text(cursor, banner) # Only do this once - self.sig_prompt_ready.disconnect(self._show_banner) + self._is_banner_shown = True # ---- Private API (overrode by us) def _event_filter_console_keypress(self, event): @@ -1465,3 +1474,11 @@ def focusOutEvent(self, event): """Reimplement Qt method to send focus change notification""" self.sig_focus_changed.emit() return super(ShellWidget, self).focusOutEvent(event) + + # ---- Python methods + def __repr__(self): + # Handy repr for logging. + # Solution from https://stackoverflow.com/a/121508/438386 + return ( + "<" + self.__class__.__name__ + " object at " + hex(id(self)) + ">" + ) diff --git a/spyder/plugins/ipythonconsole/widgets/status.py b/spyder/plugins/ipythonconsole/widgets/status.py index ffbc93a74d2..e41c60a7ed0 100644 --- a/spyder/plugins/ipythonconsole/widgets/status.py +++ b/spyder/plugins/ipythonconsole/widgets/status.py @@ -100,6 +100,8 @@ def update_matplotlib_gui(self, gui, shellwidget=None): # ------------------------------------------------------------------------- def update_status(self, gui): """Update interactive state.""" + logger.debug(f"Setting Matplotlib backend to {gui}") + if self._interactive_gui is None and gui != "inline": self._interactive_gui = gui self._gui = gui