From 710696ae4e795032195115efb5d201b2abaceb1a Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 26 Mar 2023 13:37:45 -0500 Subject: [PATCH 1/3] Testing: Run test suite with Jupyter-client 8 --- .github/scripts/install.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/scripts/install.sh b/.github/scripts/install.sh index 42b373fe8cb..e857b317668 100755 --- a/.github/scripts/install.sh +++ b/.github/scripts/install.sh @@ -22,6 +22,9 @@ if [ "$USE_CONDA" = "true" ]; then # To check our manifest and coverage micromamba install check-manifest -c conda-forge codecov -q -y + + # To test with Jupyter-client 8 + micromamba install jupyter_client=8.1 else # Update pip and setuptools python -m pip install -U pip setuptools wheel build @@ -43,6 +46,9 @@ else pip uninstall pyqt5 pyqt5-qt5 pyqt5-sip pyqtwebengine pyqtwebengine-qt5 -q -y pip install pyqt5==5.12.* pyqtwebengine==5.12.* fi + + # To test with Jupyter-client 8 + pip install jupyter-client==8.1 fi # Install subrepos from source From 7e67e2e1f9930058dd7242465c434a4fdcbcc9f2 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 28 Mar 2023 17:01:05 -0500 Subject: [PATCH 2/3] git subrepo pull (merge) --remote=https://github.com/spyder-ide/spyder-kernels.git --branch=2.x --update --force external-deps/spyder-kernels subrepo: subdir: "external-deps/spyder-kernels" merged: "0f2164c25" upstream: origin: "https://github.com/spyder-ide/spyder-kernels.git" branch: "2.x" commit: "0f2164c25" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo" commit: "2f68596" --- external-deps/spyder-kernels/.gitrepo | 4 ++-- .../spyder-kernels/requirements/posix.txt | 2 +- .../spyder-kernels/requirements/python-27.txt | 2 +- .../spyder-kernels/requirements/windows.txt | 2 +- external-deps/spyder-kernels/setup.py | 4 ++-- .../spyder_kernels/console/kernel.py | 8 +------- .../spyder_kernels/console/start.py | 18 ++++-------------- 7 files changed, 12 insertions(+), 28 deletions(-) diff --git a/external-deps/spyder-kernels/.gitrepo b/external-deps/spyder-kernels/.gitrepo index fde8d437136..230c11a27ec 100644 --- a/external-deps/spyder-kernels/.gitrepo +++ b/external-deps/spyder-kernels/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/spyder-ide/spyder-kernels.git branch = 2.x - commit = 7156204effd95241c4553f27530eb5e6d12d52e8 - parent = 5f63db63189dc82b1de481bc8bf61733d7c24df2 + commit = 0f2164c25f36890aca865a0bddf419c774de1bb3 + parent = 710696ae4e795032195115efb5d201b2abaceb1a method = merge cmdver = 0.4.3 diff --git a/external-deps/spyder-kernels/requirements/posix.txt b/external-deps/spyder-kernels/requirements/posix.txt index a1c8948aa1c..11fa49b8896 100644 --- a/external-deps/spyder-kernels/requirements/posix.txt +++ b/external-deps/spyder-kernels/requirements/posix.txt @@ -1,6 +1,6 @@ cloudpickle ipykernel>=6.16.1,<7 ipython>=7.31.1,<9 -jupyter_client>=7.4.9,<8 +jupyter_client>=7.4.9,<9 pyzmq>=22.1.0 wurlitzer>=1.0.3 diff --git a/external-deps/spyder-kernels/requirements/python-27.txt b/external-deps/spyder-kernels/requirements/python-27.txt index 9dfccfef328..c0d798825c3 100644 --- a/external-deps/spyder-kernels/requirements/python-27.txt +++ b/external-deps/spyder-kernels/requirements/python-27.txt @@ -1,7 +1,7 @@ decorator<5 backports.functools_lru_cache cloudpickle -ipykernel<5 +ipykernel>=4.5,<5 jupyter_client>=5.3.4,<6 pyzmq>=17,<20 wurlitzer>=1.0.3 diff --git a/external-deps/spyder-kernels/requirements/windows.txt b/external-deps/spyder-kernels/requirements/windows.txt index 28268954b7a..da1002d040f 100644 --- a/external-deps/spyder-kernels/requirements/windows.txt +++ b/external-deps/spyder-kernels/requirements/windows.txt @@ -1,5 +1,5 @@ cloudpickle ipykernel>=6.16.1,<7 ipython>=7.31.1,<9 -jupyter_client>=7.4.9,<8 +jupyter_client>=7.4.9,<9 pyzmq>=22.1.0 diff --git a/external-deps/spyder-kernels/setup.py b/external-deps/spyder-kernels/setup.py index 5736045c1b0..dd4b47e3efa 100644 --- a/external-deps/spyder-kernels/setup.py +++ b/external-deps/spyder-kernels/setup.py @@ -39,12 +39,12 @@ def get_version(module='spyder_kernels'): 'decorator<5; python_version<"3"', 'backports.functools-lru-cache; python_version<"3"', 'cloudpickle', - 'ipykernel<5; python_version<"3"', + 'ipykernel>=4.5,<5; python_version<"3"', 'ipykernel>=6.16.1,<7; python_version>="3"', 'ipython<6; python_version<"3"', 'ipython>=7.31.1,<9,!=8.8.0,!=8.9.0,!=8.10.0; python_version>="3"', 'jupyter-client>=5.3.4,<6; python_version<"3"', - 'jupyter-client>=7.4.9,<8; python_version>="3"', + 'jupyter-client>=7.4.9,<9; python_version>="3"', 'pyzmq>=17,<20; python_version<"3"', 'pyzmq>=22.1.0; python_version>="3"', 'wurlitzer>=1.0.3;platform_system!="Windows"', diff --git a/external-deps/spyder-kernels/spyder_kernels/console/kernel.py b/external-deps/spyder-kernels/spyder_kernels/console/kernel.py index 6aba2210a0e..306d2afab2d 100644 --- a/external-deps/spyder-kernels/spyder_kernels/console/kernel.py +++ b/external-deps/spyder-kernels/spyder_kernels/console/kernel.py @@ -11,14 +11,12 @@ """ # Standard library imports -from distutils.version import LooseVersion import logging import os import sys import threading # Third-party imports -import ipykernel from ipykernel.ipkernel import IPythonKernel from ipykernel import eventloops from traitlets.config.loader import LazyConfigValue @@ -450,11 +448,7 @@ def set_mpl_inline_figure_format(self, figure_format): def set_mpl_inline_resolution(self, resolution): """Set inline figure resolution.""" - if LooseVersion(ipykernel.__version__) < LooseVersion('4.5'): - option = 'savefig.dpi' - else: - option = 'figure.dpi' - self._set_mpl_inline_rc_config(option, resolution) + self._set_mpl_inline_rc_config('figure.dpi', resolution) def set_mpl_inline_figure_size(self, width, height): """Set inline figure size.""" diff --git a/external-deps/spyder-kernels/spyder_kernels/console/start.py b/external-deps/spyder-kernels/spyder_kernels/console/start.py index baa7f924fa3..02a2710a3ca 100644 --- a/external-deps/spyder-kernels/spyder_kernels/console/start.py +++ b/external-deps/spyder-kernels/spyder_kernels/console/start.py @@ -11,7 +11,6 @@ """ # Standard library imports -from distutils.version import LooseVersion import os import os.path as osp import sys @@ -73,7 +72,6 @@ def sympy_config(mpl_backend): def kernel_config(): """Create a config object with IPython kernel options.""" - import ipykernel from IPython.core.application import get_ipython_dir from traitlets.config.loader import Config, load_pyconfig_files @@ -149,19 +147,11 @@ def kernel_config(): # use our config system to configure the # inline backend but want to use # '%matplotlib inline' at runtime - if LooseVersion(ipykernel.__version__) < LooseVersion('4.5'): - dpi_option = 'savefig.dpi' - else: - dpi_option = 'figure.dpi' - - # The typical default figure size is too large for inline use, - # so we shrink the figure size to 6x4, and tweak fonts to - # make that fit. spy_cfg.InlineBackend.rc = { 'figure.figsize': (6.0, 4.0), # 72 dpi matches SVG/qtconsole. # This only affects PNG export, as SVG has no dpi setting. - dpi_option: 72, + 'figure.dpi': 72, # 12pt labels get cutoff on 6x4 logplots, so use 10pt. 'font.size': 10, # 10pt still needs a little more room on the xlabel @@ -190,7 +180,8 @@ def kernel_config(): # Resolution resolution_o = os.environ.get('SPY_RESOLUTION_O') if resolution_o is not None: - spy_cfg.InlineBackend.rc[dpi_option] = float(resolution_o) + spy_cfg.InlineBackend.rc['figure.dpi'] = float( + resolution_o) # Figure size width_o = float(os.environ.get('SPY_WIDTH_O')) @@ -248,8 +239,7 @@ def kernel_config(): # Disable the new mechanism to capture and forward low-level output # in IPykernel 6. For that we have Wurlitzer. - if LooseVersion(ipykernel.__version__) >= LooseVersion('6.3.0'): - spy_cfg.IPKernelApp.capture_fd_output = False + spy_cfg.IPKernelApp.capture_fd_output = False # Merge IPython and Spyder configs. Spyder prefs will have prevalence # over IPython ones From 6bcd55cb3c27d1d95b0402ec44f7c8fc4204ab42 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Tue, 28 Mar 2023 17:02:02 -0500 Subject: [PATCH 3/3] git subrepo pull (merge) --remote=https://github.com/jupyter/qtconsole.git --branch=master --update --force external-deps/qtconsole subrepo: subdir: "external-deps/qtconsole" merged: "729a87a3e" upstream: origin: "https://github.com/jupyter/qtconsole.git" branch: "master" commit: "729a87a3e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo" commit: "2f68596" --- external-deps/qtconsole/.gitrepo | 4 +- .../qtconsole/docs/source/changelog.rst | 8 ++++ external-deps/qtconsole/qtconsole/client.py | 28 +++--------- .../qtconsole/qtconsole/frontend_widget.py | 8 +++- .../qtconsole/qtconsole/jupyter_widget.py | 4 -- external-deps/qtconsole/qtconsole/manager.py | 18 ++++++++ .../qtconsole/qtconsole/qtconsoleapp.py | 6 ++- .../qtconsole/tests/test_00_console_widget.py | 44 +++++++++++++++++++ external-deps/qtconsole/setup.py | 3 +- 9 files changed, 90 insertions(+), 33 deletions(-) diff --git a/external-deps/qtconsole/.gitrepo b/external-deps/qtconsole/.gitrepo index 70cb69ffb03..7d8fcdad34a 100644 --- a/external-deps/qtconsole/.gitrepo +++ b/external-deps/qtconsole/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/jupyter/qtconsole.git branch = master - commit = 96604f44cdbca051712b5c51072309cba9518ae7 - parent = be96be0cfc313a34097505631502e711af94071d + commit = 729a87a3e6e8d63da22b82bcc3b7d60b6cabee29 + parent = 7e67e2e1f9930058dd7242465c434a4fdcbcc9f2 method = merge cmdver = 0.4.3 diff --git a/external-deps/qtconsole/docs/source/changelog.rst b/external-deps/qtconsole/docs/source/changelog.rst index f5e5fc4af7d..cc659645799 100644 --- a/external-deps/qtconsole/docs/source/changelog.rst +++ b/external-deps/qtconsole/docs/source/changelog.rst @@ -8,6 +8,14 @@ Changes in Jupyter Qt console 5.4 ~~~ +5.4.1 +----- + +`5.4.1 on GitHub `__ + +* Fix crash at startup with PySide6. +* Cast images width and height to int when trying to insert them. + 5.4.0 ----- diff --git a/external-deps/qtconsole/qtconsole/client.py b/external-deps/qtconsole/qtconsole/client.py index 58b0cb3fb2b..9306e859ea0 100644 --- a/external-deps/qtconsole/qtconsole/client.py +++ b/external-deps/qtconsole/qtconsole/client.py @@ -1,28 +1,17 @@ """ Defines a KernelClient that provides signals and slots. """ -import atexit -import errno -from threading import Thread -import time -import zmq -# import ZMQError in top-level namespace, to avoid ugly attribute-error messages -# during garbage collection of threads at exit: -from zmq import ZMQError -from zmq.eventloop import ioloop, zmqstream - -from qtpy import QtCore - -# Local imports -from traitlets import Type, Instance +# Third-party imports from jupyter_client.channels import HBChannel -from jupyter_client import KernelClient -from jupyter_client.channels import InvalidPortNumber from jupyter_client.threaded import ThreadedKernelClient, ThreadedZMQSocketChannel +from qtpy import QtCore +from traitlets import Type +# Local imports from .kernel_mixins import QtKernelClientMixin from .util import SuperQObject + class QtHBChannel(SuperQObject, HBChannel): # A longer timeout than the base class time_to_dead = 3.0 @@ -36,11 +25,8 @@ def call_handlers(self, since_last_heartbeat): # Emit the generic signal. self.kernel_died.emit(since_last_heartbeat) -from jupyter_client import protocol_version_info - -major_protocol_version = protocol_version_info[0] -class QtZMQSocketChannel(ThreadedZMQSocketChannel,SuperQObject): +class QtZMQSocketChannel(ThreadedZMQSocketChannel, SuperQObject): """A ZMQ socket emitting a Qt signal when a message is received.""" message_received = QtCore.Signal(object) @@ -49,7 +35,6 @@ def process_events(self): """ QtCore.QCoreApplication.instance().processEvents() - def call_handlers(self, msg): """This method is called in the ioloop thread when a message arrives. @@ -64,7 +49,6 @@ def call_handlers(self, msg): class QtKernelClient(QtKernelClientMixin, ThreadedKernelClient): """ A KernelClient that provides signals and slots. """ - iopub_channel_class = Type(QtZMQSocketChannel) shell_channel_class = Type(QtZMQSocketChannel) stdin_channel_class = Type(QtZMQSocketChannel) diff --git a/external-deps/qtconsole/qtconsole/frontend_widget.py b/external-deps/qtconsole/qtconsole/frontend_widget.py index 02860bb6fd6..0978f2d383b 100644 --- a/external-deps/qtconsole/qtconsole/frontend_widget.py +++ b/external-deps/qtconsole/qtconsole/frontend_widget.py @@ -538,6 +538,12 @@ def _handle_kernel_restarted(self, died=True): """ self.log.warning("kernel restarted") self._kernel_restarted_message(died=died) + + # This resets the autorestart counter so that the kernel can be + # auto-restarted before the next time it's polled to see if it's alive. + if self.kernel_manager: + self.kernel_manager.reset_autorestart_count() + self.reset() def _handle_inspect_reply(self, rep): @@ -831,8 +837,6 @@ def _document_contents_change(self, position, removed, added): """ # Calculate where the cursor should be *after* the change: position += added - - document = self._control.document() if position == self._get_cursor().position(): self._auto_call_tip() diff --git a/external-deps/qtconsole/qtconsole/jupyter_widget.py b/external-deps/qtconsole/qtconsole/jupyter_widget.py index 1e3f33dc7ee..cc9de8770b7 100644 --- a/external-deps/qtconsole/qtconsole/jupyter_widget.py +++ b/external-deps/qtconsole/qtconsole/jupyter_widget.py @@ -7,12 +7,9 @@ # Distributed under the terms of the Modified BSD License. from collections import namedtuple -import os.path -import re from subprocess import Popen import sys import time -from textwrap import dedent from warnings import warn from qtpy import QtCore, QtGui @@ -287,7 +284,6 @@ def _handle_display_data(self, msg): if self.include_output(msg): self.flush_clearoutput() data = msg['content']['data'] - metadata = msg['content']['metadata'] # In the regular JupyterWidget, we simply print the plain text # representation. if 'text/plain' in data: diff --git a/external-deps/qtconsole/qtconsole/manager.py b/external-deps/qtconsole/qtconsole/manager.py index cb6d2c61781..1e61254794c 100644 --- a/external-deps/qtconsole/qtconsole/manager.py +++ b/external-deps/qtconsole/qtconsole/manager.py @@ -26,6 +26,9 @@ def stop(self): def poll(self): super().poll() + def reset_count(self): + self._restart_count = 0 + class QtKernelManager(KernelManager, QtKernelManagerMixin): """A KernelManager with Qt signals for restart""" @@ -62,6 +65,21 @@ def post_start_kernel(self, **kw): self.kernel_restarted.emit() self._is_restarting = False + def reset_autorestart_count(self): + """Reset autorestart count.""" + if self._restarter: + self._restarter.reset_count() + + async def _async_post_start_kernel(self, **kw): + """ + This is necessary for Jupyter-client 8+ because `start_kernel` doesn't + call `post_start_kernel` directly. + """ + await super()._async_post_start_kernel(**kw) + if self._is_restarting: + self.kernel_restarted.emit() + self._is_restarting = False + def _handle_kernel_restarting(self): """Kernel has died, and will be restarted.""" self._is_restarting = True diff --git a/external-deps/qtconsole/qtconsole/qtconsoleapp.py b/external-deps/qtconsole/qtconsole/qtconsoleapp.py index 326f0adafab..cee348d6deb 100644 --- a/external-deps/qtconsole/qtconsole/qtconsoleapp.py +++ b/external-deps/qtconsole/qtconsole/qtconsoleapp.py @@ -12,6 +12,8 @@ import sys from warnings import warn +from packaging.version import parse + # If run on Windows: # # 1. Install an exception hook which pops up a message box. @@ -416,8 +418,8 @@ def initialize(self, argv=None): # Fixes launching issues with Big Sur # https://bugreports.qt.io/browse/QTBUG-87014, fixed in qt 5.15.2 if sys.platform == 'darwin': - v_5_15_2 = QtCore.QVersionNumber.fromString('5.15.2')[0] - v_current = QtCore.QVersionNumber.fromString(QT_VERSION)[0] + v_5_15_2 = parse('5.15.2') + v_current = parse(QT_VERSION) if v_current < v_5_15_2: os.environ['QT_MAC_WANTS_LAYER'] = '1' self._init_asyncio_patch() diff --git a/external-deps/qtconsole/qtconsole/tests/test_00_console_widget.py b/external-deps/qtconsole/qtconsole/tests/test_00_console_widget.py index 40f9a0d7da9..2745be6ee9b 100644 --- a/external-deps/qtconsole/qtconsole/tests/test_00_console_widget.py +++ b/external-deps/qtconsole/qtconsole/tests/test_00_console_widget.py @@ -1,3 +1,4 @@ +import os import unittest import sys @@ -259,6 +260,49 @@ def wait_for_input(): assert output in control.toPlainText() +@flaky(max_runs=5) +@pytest.mark.skipif(os.name == 'nt', reason="no SIGTERM on Windows") +def test_restart_after_kill(qtconsole, qtbot): + """ + Test that the kernel correctly restarts after a kill. + """ + window = qtconsole.window + shell = window.active_frontend + control = shell._control + + def wait_for_restart(): + qtbot.waitUntil( + lambda: 'Kernel died, restarting' in control.toPlainText() + ) + + # Wait until the console is fully up + qtbot.waitUntil(lambda: shell._prompt_html is not None, + timeout=SHELL_TIMEOUT) + + # This checks that we are able to restart the kernel even after the number + # of consecutive auto-restarts is reached (which by default is five). + for _ in range(10): + # Clear console + with qtbot.waitSignal(shell.executed): + shell.execute('%clear') + qtbot.wait(500) + + # Run some code that kills the kernel + code = "import os, signal; os.kill(os.getpid(), signal.SIGTERM)" + shell.execute(code) + + # Check that the restart message is printed + qtbot.waitUntil( + lambda: 'Kernel died, restarting' in control.toPlainText() + ) + + # Check that a new prompt is available after the restart + qtbot.waitUntil( + lambda: control.toPlainText().splitlines()[-1] == 'In [1]: ' + ) + qtbot.wait(500) + + @pytest.mark.skipif(no_display, reason="Doesn't work without a display") class TestConsoleWidget(unittest.TestCase): diff --git a/external-deps/qtconsole/setup.py b/external-deps/qtconsole/setup.py index b858ce2d1fe..5342906a3c0 100644 --- a/external-deps/qtconsole/setup.py +++ b/external-deps/qtconsole/setup.py @@ -73,7 +73,8 @@ 'pygments', 'ipykernel>=4.1', # not a real dependency, but require the reference kernel 'qtpy>=2.0.1', - 'pyzmq>=17.1' + 'pyzmq>=17.1', + 'packaging' ], extras_require = { 'test': ['flaky', 'pytest', 'pytest-qt'],